mirror of
https://github.com/gnosygnu/xowa.git
synced 2024-10-27 20:34:16 +00:00
ParserFunctions: Support timezone / meridian formats [#648]
This commit is contained in:
parent
9cb60f2540
commit
763c7b52b7
@ -85,6 +85,9 @@ public class DateAdp implements CompareAble, Gfo_invk {
|
||||
: Timezone_offset_test
|
||||
;
|
||||
}
|
||||
public String Timezone_id() {
|
||||
return "UTC"; // under.getTimeZone().getID(); // NOTE: timezone is always UTC, unless over-ridden by tests
|
||||
}
|
||||
public DateAdp XtoUtc() {
|
||||
java.util.Date date = under.getTime();
|
||||
java.util.TimeZone tz = under.getTimeZone();
|
||||
@ -109,6 +112,7 @@ public class DateAdp implements CompareAble, Gfo_invk {
|
||||
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);}
|
||||
|
@ -60,6 +60,9 @@ public class DateAdp__tst {
|
||||
@Test public void XtoUtc() {
|
||||
fxt.Test__to_utc("2012-01-01 00:00", "2012-01-01 05:00"); //4=Wed
|
||||
}
|
||||
@Test public void Timezone_id() {
|
||||
fxt.Test__timezone_id("2015-12-26T10:03:53Z", "UTC");
|
||||
}
|
||||
}
|
||||
class DateAdp__fxt {
|
||||
public void Test__parse_gplx(String raw, String expd) {
|
||||
@ -83,4 +86,7 @@ class DateAdp__fxt {
|
||||
public void Test__to_utc(String raw, String expd) {
|
||||
Tfds.Eq(expd, DateAdp_.parse_gplx(raw).XtoUtc().XtoStr_fmt_yyyy_MM_dd_HH_mm());
|
||||
}
|
||||
public void Test__timezone_id(String raw, String expd) {
|
||||
Gftest.Eq__str(expd, DateAdp_.parse_gplx(raw).XtoUtc().Timezone_id());
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ public class Pft_fmt_itm_ {
|
||||
, Tid_hijiri_month_idx = 31
|
||||
, Tid_hijiri_day_idx = 32
|
||||
, Tid_hijiri_month_name = 33
|
||||
, Tid_timezone_id_full = 35
|
||||
, Tid_timezone_id_abrv = 36
|
||||
, Tid_timezone_offset_4 = 37
|
||||
, Tid_timezone_offset_4_colon = 38
|
||||
;
|
||||
|
||||
public static final Pft_fmt_itm
|
||||
@ -107,6 +111,10 @@ public class Pft_fmt_itm_ {
|
||||
, Hijiri_month_idx = new Pft_fmt_itm_hijiri_month_idx()
|
||||
, Hijiri_day_idx = new Pft_fmt_itm_hijiri_day_idx()
|
||||
, Hijiri_month_name = new Pft_fmt_itm_hijiri_month_name()
|
||||
, Timezone_id_full = new Pft_fmt_itm_timezone_id(Bool_.N)
|
||||
, Timezone_id_abrv = new Pft_fmt_itm_timezone_id(Bool_.Y)
|
||||
, Timezone_offset_4 = new Pft_fmt_itm_timezone_offset_4(Bool_.N)
|
||||
, Timezone_offset_4_colon = new Pft_fmt_itm_timezone_offset_4(Bool_.Y)
|
||||
;
|
||||
public static final Btrie_fast_mgr Regy = Btrie_fast_mgr.cs()
|
||||
.Add(Byte_ascii.Ltr_Y , Pft_fmt_itm_.Year_len4) // 2012
|
||||
@ -139,6 +147,10 @@ public class Pft_fmt_itm_ {
|
||||
.Add(Byte_ascii.Ltr_Z , Pft_fmt_itm_.Timezone_offset) // timezone offset in seconds
|
||||
.Add(Byte_ascii.Ltr_c , Pft_fmt_itm_.Iso_fmt) // 2012-01-02T03:04:05+00:00
|
||||
.Add(Byte_ascii.Ltr_r , Pft_fmt_itm_.Rfc_5322) // Mon 02 Jan 2012 08:04:05 +0000
|
||||
.Add(Byte_ascii.Ltr_e , Pft_fmt_itm_.Timezone_id_full) // UTC
|
||||
.Add(Byte_ascii.Ltr_T , Pft_fmt_itm_.Timezone_id_abrv) // UTC
|
||||
.Add(Byte_ascii.Ltr_O , Pft_fmt_itm_.Timezone_offset_4) // timezone offset in seconds +0000
|
||||
.Add(Byte_ascii.Ltr_P , Pft_fmt_itm_.Timezone_offset_4_colon) // timezone offset in seconds with colon +00:00
|
||||
.Add("xr" , Pft_fmt_itm_.Roman) // MCXI
|
||||
.Add("xkY" , Pft_fmt_itm_.Thai) // Year += 543
|
||||
.Add("xoY" , Pft_fmt_itm_.Minguo) // Year -= 1911
|
||||
|
@ -81,12 +81,14 @@ class Pft_fmt_itm_dayOfYear implements Pft_fmt_itm {
|
||||
class Pft_fmt_itm_am_pm implements Pft_fmt_itm {
|
||||
public int TypeId() {return Pft_fmt_itm_.Tid_AmPm;}
|
||||
public void Fmt(Bry_bfr bfr, Xowe_wiki wiki, Xol_lang_itm lang, DateAdp date, Pft_func_formatdate_bldr bldr) {
|
||||
boolean am = date.Hour() < 13;
|
||||
boolean am = date.Hour() < 12;
|
||||
byte[] val = null;
|
||||
if ( am && lower) val = Ary_am_lower;
|
||||
else if ( am && !lower) val = Ary_am_upper;
|
||||
else if (!am && lower) val = Ary_pm_lower;
|
||||
else if (!am && !lower) val = Ary_pm_upper;
|
||||
if (am) {
|
||||
val = lower ? Ary_am_lower : Ary_am_upper;
|
||||
}
|
||||
else {
|
||||
val = lower ? Ary_pm_lower : Ary_pm_upper;
|
||||
}
|
||||
bfr.Add(val);
|
||||
} private static final byte[] Ary_am_upper = Bry_.new_a7("AM"), Ary_pm_upper = Bry_.new_a7("PM"), Ary_am_lower = Bry_.new_a7("am"), Ary_pm_lower = Bry_.new_a7("pm");
|
||||
public Pft_fmt_itm_am_pm(boolean lower) {this.lower = lower;} private boolean lower;
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
XOWA: the XOWA Offline Wiki Application
|
||||
Copyright (C) 2012-2017 gnosygnu@gmail.com
|
||||
|
||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
||||
or alternatively under the terms of the Apache License Version 2.0.
|
||||
|
||||
You may use XOWA according to either of these licenses as is most appropriate
|
||||
for your project on a case-by-case basis.
|
||||
|
||||
The terms of each license can be found in the source code repository:
|
||||
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.xtns.pfuncs.times; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
|
||||
import gplx.xowa.langs.*;
|
||||
class Pft_fmt_itm_timezone_offset_4 implements Pft_fmt_itm {
|
||||
private final boolean colon;
|
||||
public Pft_fmt_itm_timezone_offset_4(boolean colon) {this.colon = colon;}
|
||||
public int TypeId() {return Pft_fmt_itm_.Tid_timezone_offset_4;}
|
||||
public void Fmt(Bry_bfr bfr, Xowe_wiki wiki, Xol_lang_itm lang, DateAdp date, Pft_func_formatdate_bldr bldr) {
|
||||
// get tz_secs
|
||||
int tz_secs = date.Timezone_offset();
|
||||
|
||||
// add "+" or "-"
|
||||
if (tz_secs < 0) {
|
||||
bfr.Add_byte(Byte_ascii.Dash);
|
||||
tz_secs *= -1;
|
||||
}
|
||||
else {
|
||||
bfr.Add_byte(Byte_ascii.Plus);
|
||||
}
|
||||
|
||||
// calc mins / hours
|
||||
int tz_mins = tz_secs / 60;
|
||||
|
||||
// add bfr
|
||||
bfr.Add_int_fixed((tz_mins / 60), 2);
|
||||
if (colon)
|
||||
bfr.Add_byte(Byte_ascii.Colon);
|
||||
bfr.Add_int_fixed((tz_mins % 60), 2);
|
||||
}
|
||||
}
|
||||
class Pft_fmt_itm_timezone_id implements Pft_fmt_itm {
|
||||
public Pft_fmt_itm_timezone_id(boolean abrv) {}
|
||||
public int TypeId() {return Pft_fmt_itm_.Tid_timezone_id_full;}
|
||||
public void Fmt(Bry_bfr bfr, Xowe_wiki wiki, Xol_lang_itm lang, DateAdp date, Pft_func_formatdate_bldr bldr) {
|
||||
bfr.Add_str_a7(date.Timezone_id());
|
||||
}
|
||||
}
|
@ -107,4 +107,65 @@ public class Pft_func_time__basic__tst {
|
||||
@Test public void Iso8601_T() {fxt.Test_parse_tmpl_str("{{#time:Y-m-d h:i:s A|T1:23}}" , "2012-01-02 01:23:00 AM");} // handle "T" flag; PAGE:pl.w:StarCraft_II:_Wings_of_Liberty
|
||||
@Test public void Iso8601_T_ws() {fxt.Test_parse_tmpl_str("{{#time:Y-m-d h:i:s A|T 1:23}}" , "2012-01-02 01:23:00 AM");} // handle "T" flag and ws
|
||||
@Test public void Iso8601_T_fail() {fxt.Test_parse_tmpl_str("{{#time:Y-m-d h:i:s A|T2012-01-02}}" , "<strong class=\"error\">Invalid hour: T</strong>");} // handle "T" flag and ws
|
||||
|
||||
@Test public void Timezone_id() {// hard-coded to return "UTC"; DATE:2020-01-18
|
||||
fxt.Test_parse_tmpl_str("{{#time:e|2012-01-02 03:04:05}}", "UTC");
|
||||
}
|
||||
@Test public void Timezone_abrv() { // hard-coded to return "UTC"; DATE:2020-01-18
|
||||
fxt.Test_parse_tmpl_str("{{#time:T|2012-01-02 03:04:05}}", "UTC");
|
||||
}
|
||||
@Test public void Timezone_offset_4() { // hard-coded to return "+0000"; DATE:2020-01-18
|
||||
fxt.Test_parse_tmpl_str("{{#time:O|2012-01-02 03:04:05}}", "+0000");
|
||||
}
|
||||
@Test public void Timezone_offset_4_colon() { // hard-coded to return "+00:00"; DATE:2020-01-18
|
||||
fxt.Test_parse_tmpl_str("{{#time:O|2012-01-02 03:04:05}}", "+0000");
|
||||
}
|
||||
@Test public void Timezone_offset_4__vals() {
|
||||
Pft_func_time__fxt func_fxt = new Pft_func_time__fxt(fxt);
|
||||
func_fxt.Test__parse_w_offset_mins( 60, "{{#time:O|2012-01-02 03:04:05}}", "+0100");
|
||||
func_fxt.Test__parse_w_offset_mins(-300, "{{#time:O|2012-01-02 03:04:05}}", "-0500");
|
||||
func_fxt.Test__parse_w_offset_mins( 330, "{{#time:O|2012-01-02 03:04:05}}", "+0530");
|
||||
}
|
||||
@Test public void Meridian__AM__bugfix() {
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|2012-01-02 12:00:00}}", "PM");
|
||||
}
|
||||
@Test public void Meridian__AM() {
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|12:00 AM}}", "AM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|12:00 a.m}}", "AM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|12:00 am.}}", "AM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|12:00 a.m.}}", "AM");
|
||||
}
|
||||
@Test public void Meridian__PM() {
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|2:00 PM}}", "PM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|2:00 p.m}}", "PM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|2:00 pm.}}", "PM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:A|2:00 p.m.}}", "PM");
|
||||
}
|
||||
@Test public void Meridian__basic() {
|
||||
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1PM}}" , "01:00:00 PM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1 PM}}" , "01:00:00 PM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1:02 PM}}" , "01:02:00 PM");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1:02:03 PM}}" , "01:02:03 PM");
|
||||
}
|
||||
@Test public void Meridian__errors() {
|
||||
fxt.Test_parse_tmpl_str("{{#time:h|PM}}" , "<strong class=\"error\">Invalid time. Nothing found before meridian</strong>");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h|monday PM}}" , "<strong class=\"error\">Invalid time. Text found before meridian</strong>");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h|0 AM}}" , "<strong class=\"error\">Invalid time. Invalid digit for meridian: 0</strong>");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h|13 AM}}" , "<strong class=\"error\">Invalid time. Invalid digit for meridian: 13</strong>");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h|1 :00 AM}}" , "<strong class=\"error\">Invalid time. Text found before 1st colon</strong>");
|
||||
fxt.Test_parse_tmpl_str("{{#time:h|1 :00:00 AM}}" , "<strong class=\"error\">Invalid time. Text found before 2nd colon</strong>");
|
||||
}
|
||||
}
|
||||
class Pft_func_time__fxt {
|
||||
private final Xop_fxt fxt;
|
||||
public Pft_func_time__fxt(Xop_fxt fxt) {this.fxt = fxt;}
|
||||
public void Test__parse_w_offset_mins(int offset, String raw, String expd) {
|
||||
try {
|
||||
DateAdp.Timezone_offset_test = offset * 60;
|
||||
fxt.Test_parse_tmpl_str(raw, expd);
|
||||
}
|
||||
finally {
|
||||
DateAdp.Timezone_offset_test = Int_.Min_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ class Pxd_itm_ {
|
||||
, Tid_unit_relative = 11 // next, previous
|
||||
, Tid_unixtime = 12 // @123
|
||||
, Tid_iso8601_t = 13 // T
|
||||
, Tid_meridian = 14 // PM
|
||||
, Tid_dash = Byte_ascii.Dash
|
||||
, Tid_dot = Byte_ascii.Dot
|
||||
, Tid_slash = Byte_ascii.Slash
|
||||
@ -101,5 +102,6 @@ class Pft_func_time_log {
|
||||
, Invalid_second = Gfo_msg_itm_.new_warn_(owner, "Invalid second: ~{0}")
|
||||
, Invalid_date = Gfo_msg_itm_.new_warn_(owner, "Invalid date: ~{0}")
|
||||
, Invalid_timezone = Gfo_msg_itm_.new_warn_(owner, "Invalid timezone: ~{0}")
|
||||
, Invalid_time = Gfo_msg_itm_.new_warn_(owner, "Invalid time. ~{0}")
|
||||
;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class Pxd_itm_int extends Pxd_itm_base implements Pxd_itm_int_interface {
|
||||
public int Val() {return val;} public Pxd_itm_int Val_(int v) {val = v; return this;} private int val;
|
||||
public boolean Val_is_adj() {return val_is_adj;} public void Val_is_adj_(boolean v) {val_is_adj = v;} private boolean val_is_adj;
|
||||
public int Xto_int_or(int or) {return val;}
|
||||
public int Digits() {return digits;} private int digits;
|
||||
public int Digits() {return digits;} private int digits; // NOTE: digits exists primarily for year evaluation; EX: 11 vs 2011
|
||||
@Override public boolean Time_ini(Pxd_date_bldr bldr) {
|
||||
int seg_idx = this.Seg_idx();
|
||||
if (seg_idx == Pxd_itm_base.Seg_idx_skip) return true;
|
||||
|
113
400_xowa/src/gplx/xowa/xtns/pfuncs/times/Pxd_itm_meridian.java
Normal file
113
400_xowa/src/gplx/xowa/xtns/pfuncs/times/Pxd_itm_meridian.java
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
XOWA: the XOWA Offline Wiki Application
|
||||
Copyright (C) 2012-2017 gnosygnu@gmail.com
|
||||
|
||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
||||
or alternatively under the terms of the Apache License Version 2.0.
|
||||
|
||||
You may use XOWA according to either of these licenses as is most appropriate
|
||||
for your project on a case-by-case basis.
|
||||
|
||||
The terms of each license can be found in the source code repository:
|
||||
|
||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
||||
*/
|
||||
package gplx.xowa.xtns.pfuncs.times; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
|
||||
import gplx.core.brys.*;
|
||||
class Pxd_itm_meridian extends Pxd_itm_base implements Pxd_itm_prototype {
|
||||
private final boolean pm;
|
||||
public Pxd_itm_meridian(int ary_idx, boolean pm) {this.Ctor(ary_idx); this.pm = pm;}
|
||||
@Override public byte Tkn_tid() {return Pxd_itm_.Tid_meridian;}
|
||||
@Override public int Eval_idx() {return 19;}
|
||||
@Override public boolean Time_ini(Pxd_date_bldr bldr) {return true;}
|
||||
public Pxd_itm MakeNew(int ary_idx) {return new Pxd_itm_meridian(ary_idx, pm);}
|
||||
@Override public boolean Eval(Pxd_parser state) {
|
||||
Pxd_itm[] tkns = state.Tkns();
|
||||
|
||||
// init some vars
|
||||
Pxd_itm itm = null;
|
||||
boolean colon_found = false;
|
||||
|
||||
// get previous item before meridian, skipping ws
|
||||
int itm_idx;
|
||||
for (itm_idx = this.Ary_idx() - 1; itm_idx > -1; --itm_idx) {
|
||||
itm = tkns[itm_idx];
|
||||
if (itm.Tkn_tid() != Pxd_itm_.Tid_ws)
|
||||
break;
|
||||
}
|
||||
|
||||
// null-check to handle error cases like "AM"
|
||||
if (itm == null) {
|
||||
return Fail(state, "Nothing found before meridian");
|
||||
}
|
||||
else {
|
||||
// itm is not int, then err; EX: "monday AM"
|
||||
if (itm.Tkn_tid() != Pxd_itm_.Tid_int) {
|
||||
return Fail(state, "Text found before meridian");
|
||||
}
|
||||
// itm is an int
|
||||
else {
|
||||
// int_itm may be minute / seconds if there are colons; EX "12AM" vs "12:00AM" or "12:00:00AM"
|
||||
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_colon) {
|
||||
colon_found = true;
|
||||
itm_idx--;
|
||||
// make sure int exists before ":"
|
||||
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_int) {
|
||||
itm = tkns[itm_idx - 1];
|
||||
itm_idx--;
|
||||
// check again for ":"
|
||||
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_colon) {
|
||||
itm_idx--;
|
||||
// make sure int exists before ":"
|
||||
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_int) {
|
||||
itm = tkns[itm_idx - 1];
|
||||
}
|
||||
else {
|
||||
return Fail(state, "Text found before 2nd colon");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return Fail(state, "Text found before 1st colon");
|
||||
}
|
||||
}
|
||||
|
||||
// hour found
|
||||
Pxd_itm_int hour_itm = (Pxd_itm_int)itm;
|
||||
int hour = hour_itm.Val();
|
||||
|
||||
// invalid digit; fail
|
||||
if (hour == 0 || hour > 12) {
|
||||
return Fail(state, "Invalid digit for meridian: " + Int_.To_str(hour));
|
||||
}
|
||||
else {
|
||||
// update hour
|
||||
if (pm) {
|
||||
// convert "1 PM" -> hour=13
|
||||
hour_itm.Val_(hour + 12);
|
||||
}
|
||||
else { // convert "12 AM" -> 00:00
|
||||
if (hour == 12) {
|
||||
hour_itm.Val_(0);
|
||||
}
|
||||
}
|
||||
|
||||
// if no colon, eval int now else int will get eval'd as year; EX: "12 AM"
|
||||
if (!colon_found)
|
||||
return Pxd_eval_seg.Eval_as_h(state, hour_itm);
|
||||
else {
|
||||
// colon exists; don't need to do anything else b/c parser will handle normally; i.e.: "12:00 PM" is just like "12:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private boolean Fail(Pxd_parser state, String err) {
|
||||
// MW just throws "Invalid time."; add extra description message for debugging purposes
|
||||
state.Err_set(Pft_func_time_log.Invalid_time, Bfr_arg_.New_bry(err));
|
||||
return false;
|
||||
}
|
||||
}
|
@ -222,6 +222,8 @@ class Pxd_parser_ {
|
||||
Init_relative();
|
||||
trie.Add_obj(Pxd_itm_unixtime.Name_const, new Pxd_itm_unixtime(-1, -1));
|
||||
trie.Add_obj(Pxd_itm_iso8601_t.Name_const, new Pxd_itm_iso8601_t(-1, -1));
|
||||
Init_meridian(Bool_.N, "am", "a.m", "am.", "a.m.");
|
||||
Init_meridian(Bool_.Y, "pm", "p.m", "pm.", "p.m.");
|
||||
}
|
||||
private static void Init_reg_months(String[] names) {
|
||||
for (int i = 0; i < names.length; i++)
|
||||
@ -255,6 +257,11 @@ class Pxd_parser_ {
|
||||
trie.Add_obj("previous", Pxd_itm_unit_relative.Prev);
|
||||
trie.Add_obj("this", Pxd_itm_unit_relative.This);
|
||||
}
|
||||
private static void Init_meridian(boolean is_pm, String... ary) {
|
||||
Pxd_itm_meridian meridian = new Pxd_itm_meridian(-1, is_pm);
|
||||
for (String itm : ary)
|
||||
trie.Add_obj(itm, meridian);
|
||||
}
|
||||
}
|
||||
/*
|
||||
NOTE_1:parsing works by completing previous items and then setting current;
|
||||
|
Loading…
Reference in New Issue
Block a user