647 lines
16 KiB
JavaScript
647 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
var utils = require('./internal/utils.cjs');
|
|
|
|
/* eslint
|
|
no-spaced-func: 0,
|
|
no-unexpected-multiline: 0
|
|
*/
|
|
|
|
const WEEKDAYS = 'Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday'.split('|');
|
|
|
|
const lowerCaseWeekday = (weekday) => WEEKDAYS.includes(weekday) ? weekday.toLowerCase() : weekday;
|
|
|
|
const lowerCaseWeekdayWithoutDay = weekday => (weekday === 'day')
|
|
? undefined
|
|
: lowerCaseWeekday(weekday);
|
|
|
|
/**
|
|
* regular expressions to parse holiday statements
|
|
*/
|
|
const grammar = (function () {
|
|
/**
|
|
* combines different regexes
|
|
* @private
|
|
* @return {RegExp} combined regex
|
|
*/
|
|
function replace (regex, opt) {
|
|
regex = regex.source;
|
|
opt = opt || '';
|
|
return function self (name, val) {
|
|
if (!name) return new RegExp(regex, opt)
|
|
val = val.source || val;
|
|
val = val.replace(/(^|[^[])\^/g, '$1');
|
|
regex = regex.replace(name, val);
|
|
return self
|
|
}
|
|
}
|
|
|
|
// raw rules
|
|
const raw = {
|
|
_weekdays: '[Ss]unday|[Mm]onday|[Tt]uesday|[Ww]ednesday|[Tt]hursday|[Ff]riday|[Ss]aturday|day',
|
|
_months: 'January|February|March|April|May|June|July|August|September|October|November|December',
|
|
_islamicMonths: 'Muharram|Safar|Rabi al-awwal|Rabi al-thani|Jumada al-awwal|Jumada al-thani|Rajab|Shaban|Ramadan|Shawwal|Dhu al-Qidah|Dhu al-Hijjah',
|
|
_hebrewMonths: 'Nisan|Iyyar|Sivan|Tamuz|Av|Elul|Tishrei|Cheshvan|Kislev|Tevet|Shvat|AdarII|Adar',
|
|
_jalaaliMonths: 'Farvardin|Ordibehesht|Khordad|Tir|Mordad|Shahrivar|Mehr|Aban|Azar|Dey|Bahman|Esfand',
|
|
_days: /(_weekdays)s?/,
|
|
_direction: /(before|after|next|previous|in)/,
|
|
_counts: /(\d+)(?:st|nd|rd|th)?/,
|
|
_count_days: /([-+]?\d{1,2}) ?(?:days?|d)?/,
|
|
_timezone: / in ([^\s]*|[+-]\d{2}:\d{2})/,
|
|
_type: /(public|bank|school|observance|optional)/,
|
|
|
|
dateMonth: /^(_months)/,
|
|
date: /^(?:0*(\d{1,4})-)?0?(\d{1,2})-0?(\d{1,2})/,
|
|
time: /^(?:T?0?(\d{1,2}):0?(\d{1,2})|T0?(\d{1,2}))/,
|
|
duration: /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?)?/, // follows ISO 8601
|
|
|
|
julian: /^julian date/,
|
|
easter: /^(easter|orthodox)(?: _count_days)?/,
|
|
equinox: /^([Mm]arch|[Jj]une|[Ss]eptember|[Dd]ecember) (?:equinox|solstice)(?:_timezone)?/,
|
|
hebrew: /^0?(\d{1,2}) (_hebrewMonths)(?: 0*(\d{1,}))?/,
|
|
islamic: /^0?(\d{1,2}) (_islamicMonths)(?: 0*(\d{1,}))?/,
|
|
jalaali: /^0?(\d{1,2}) (_jalaaliMonths)(?: 0*(\d{1,}))?/,
|
|
chineseLunar: /^(chinese|korean|vietnamese) (?:(\d+)-(\d{1,2})-)?(\d{1,2})-([01])-(\d{1,2})/,
|
|
chineseSolar: /^(chinese|korean|vietnamese) (?:(\d+)-(\d{1,2})-)?(\d{1,2})-(\d{1,2}) solarterm/,
|
|
bengaliRevised: /^(bengali-revised) (?:-?0*(\d{1,4})-)?0?(\d{1,2})-0?(\d{1,2})/,
|
|
|
|
modifier: /^(substitutes|and|if equal|then|if)\b/,
|
|
rule_year: /^(?:in (even|odd|leap|non-leap) years|every (\d+) years? since 0*(\d{1,4}))/,
|
|
rule_weekday: /(not )?on ((?:(?:_weekdays)(?:,\s?)?)*)/,
|
|
rule_date_if_then: /^if ((?:(?:_weekdays)(?:,\s?)?)*) then (?:_direction _days)?/,
|
|
rule_day_dir_date: /^(?:_counts )?_days _direction/,
|
|
rule_bridge: /^is (?:_type )?holiday/,
|
|
rule_if_holiday: /^if is (?:_type )?holiday then (?:_counts )?(?:_direction _days)?(?: omit ((?:(?:_weekdays)(?:,\s?)?)*))?/,
|
|
rule_same_day: /^#\d+/,
|
|
rule_active_from: /^since (0*\d{1,4})(?:-0*(\d{1,2})(?:-0*(\d{1,2})|)|)(?: and|)/,
|
|
rule_active_to: /^prior to (0*\d{1,4})(?:-0*(\d{1,2})(?:-0*(\d{1,2})|)|)/,
|
|
|
|
// rule_type_if_then: /if ((?:(?:_weekdays)(?:,\s?)?)*) then/,
|
|
// rule_type_dir: /_days _direction$/,
|
|
// rule_type_bridge: / if .* is .* holiday$/,
|
|
|
|
space: /^\s+/
|
|
};
|
|
/* eslint-disable func-call-spacing */
|
|
raw._days = replace(raw._days)
|
|
(/_weekdays/, raw._weekdays)
|
|
();
|
|
raw.julian = replace(raw.julian, '')
|
|
(/date/, raw.date)
|
|
();
|
|
raw.easter = replace(raw.easter, '')
|
|
(/_count_days/, raw._count_days)
|
|
();
|
|
raw.equinox = replace(raw.equinox, '')
|
|
(/_count_days/g, raw._count_days)
|
|
(/_direction/g, raw._direction)
|
|
(/_timezone/g, raw._timezone)
|
|
();
|
|
raw.hebrew = replace(raw.hebrew, '')
|
|
(/_hebrewMonths/, raw._hebrewMonths)
|
|
();
|
|
raw.islamic = replace(raw.islamic, '')
|
|
(/_islamicMonths/, raw._islamicMonths)
|
|
();
|
|
raw.jalaali = replace(raw.jalaali, '')
|
|
(/_jalaaliMonths/, raw._jalaaliMonths)
|
|
();
|
|
raw.dateMonth = replace(raw.dateMonth)
|
|
(/_months/, raw._months)
|
|
();
|
|
|
|
raw.rule_weekday = replace(raw.rule_weekday, '')
|
|
(/_weekdays/g, raw._weekdays)
|
|
();
|
|
raw.rule_date_if_then = replace(raw.rule_date_if_then, '')
|
|
(/_direction/g, raw._direction)
|
|
(/_weekdays/g, raw._weekdays)
|
|
(/_days/g, raw._days)
|
|
();
|
|
raw.rule_bridge = replace(raw.rule_bridge, '')
|
|
(/_type/g, raw._type)
|
|
();
|
|
raw.rule_if_holiday = replace(raw.rule_if_holiday, '')
|
|
(/_type/g, raw._type)
|
|
(/_counts/g, raw._counts)
|
|
(/_direction/g, raw._direction)
|
|
(/_days/g, raw._days)
|
|
(/_weekdays/g, raw._weekdays)
|
|
();
|
|
raw.rule_day_dir_date = replace(raw.rule_day_dir_date, '')
|
|
(/_counts/, raw._counts)
|
|
(/_days/g, raw._days)
|
|
(/_direction/g, raw._direction)
|
|
();
|
|
// raw.rule_type_if_then = replace(raw.rule_type_if_then, '')
|
|
// (/_direction/g, raw._direction)
|
|
// (/_days/g, raw._days)
|
|
// ()
|
|
|
|
let i = 1;
|
|
raw.months = {};
|
|
raw._months.split('|').forEach(function (m) {
|
|
raw.months[m] = i++;
|
|
});
|
|
|
|
i = 1;
|
|
raw.islamicMonths = {};
|
|
raw._islamicMonths.split('|').forEach(function (m) {
|
|
raw.islamicMonths[m] = i++;
|
|
});
|
|
|
|
i = 1;
|
|
raw.hebrewMonths = {};
|
|
raw._hebrewMonths.split('|').forEach(function (m) {
|
|
raw.hebrewMonths[m] = i++;
|
|
});
|
|
// parser regex needs larger string before shorter AdarII and Adar pos needs correction
|
|
raw.hebrewMonths.Adar = 12;
|
|
raw.hebrewMonths.AdarII = 13;
|
|
|
|
i = 1;
|
|
raw.jalaaliMonths = {};
|
|
raw._jalaaliMonths.split('|').forEach(function (m) {
|
|
raw.jalaaliMonths[m] = i++;
|
|
});
|
|
|
|
return raw
|
|
/* eslint-enable */
|
|
})();
|
|
|
|
// console.log(grammar)
|
|
|
|
class Parser {
|
|
constructor (fns) {
|
|
this.fns = fns || [
|
|
'_julian',
|
|
'_date',
|
|
'_easter',
|
|
'_islamic',
|
|
'_hebrew',
|
|
'_jalaali',
|
|
'_equinox',
|
|
'_chineseSolar',
|
|
'_chineseLunar',
|
|
'_bengaliRevised',
|
|
'_dateMonth',
|
|
'_ruleDateIfThen',
|
|
'_ruleWeekday',
|
|
'_ruleYear',
|
|
'_ruleDateDir',
|
|
'_ruleIfHoliday',
|
|
'_ruleBridge',
|
|
'_ruleTime',
|
|
'_ruleDuration',
|
|
'_ruleModifier',
|
|
'_ruleSameDay',
|
|
'_ruleActiveFrom',
|
|
'_ruleActiveTo'
|
|
];
|
|
this.tokens = [];
|
|
}
|
|
|
|
parse (rule) {
|
|
this.setup = { str: rule, rule };
|
|
this.error = 0;
|
|
this.tokens = [];
|
|
this._tokenize(this.setup);
|
|
this._reorder();
|
|
return this.tokens
|
|
}
|
|
|
|
/**
|
|
* reorder set of tokens for rule dateDir
|
|
* dateDir: [dateDir2, dateDir1, fn] --> [fn, dateDir1, dateDir2]
|
|
* dateIfThen: [fn, dateIfThen1, dateIfThen2] --> [fn, dateIfThen1, dateIfThen2]
|
|
*/
|
|
_reorder () {
|
|
const tmp = [];
|
|
const res = [];
|
|
|
|
this.tokens.forEach((token) => {
|
|
if (token.rule === 'dateDir') {
|
|
tmp.push(token);
|
|
} else {
|
|
res.push(token);
|
|
if (tmp.length) {
|
|
while (tmp.length) {
|
|
res.push(tmp.pop());
|
|
}
|
|
}
|
|
}
|
|
|
|
// no modifiers before a date
|
|
if (token.fn && res[0].modifier) {
|
|
while (res[0].modifier) {
|
|
res.push(res.shift());
|
|
}
|
|
}
|
|
});
|
|
this.tokens = res;
|
|
}
|
|
|
|
_tokenize (o) {
|
|
let last;
|
|
while (o.str) {
|
|
for (let i = 0; i < this.fns.length; i++) {
|
|
if (this[this.fns[i]](o)) break
|
|
}
|
|
this._space(o);
|
|
if (last === o.str) {
|
|
this.error++;
|
|
break
|
|
}
|
|
last = o.str;
|
|
}
|
|
}
|
|
|
|
_shorten (o, cap0) {
|
|
o.str = o.str.substr(cap0.length, o.str.length);
|
|
}
|
|
|
|
_date (o) {
|
|
let cap;
|
|
if ((cap = grammar.date.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'gregorian',
|
|
year: utils.toNumber(cap.shift()),
|
|
month: utils.toNumber(cap.shift()),
|
|
day: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_julian (o) {
|
|
let cap;
|
|
if ((cap = grammar.julian.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'julian',
|
|
year: utils.toNumber(cap.shift()),
|
|
month: utils.toNumber(cap.shift()),
|
|
day: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_easter (o) {
|
|
let cap;
|
|
if ((cap = grammar.easter.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'easter',
|
|
type: cap.shift(),
|
|
offset: utils.toNumber(cap.shift()) || 0
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_equinox (o) {
|
|
let cap;
|
|
if ((cap = grammar.equinox.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'equinox',
|
|
season: cap.shift().toLowerCase(),
|
|
timezone: cap.shift() || 'GMT'
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_hebrew (o) {
|
|
let cap;
|
|
|
|
if ((cap = grammar.hebrew.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'hebrew',
|
|
day: utils.toNumber(cap.shift()),
|
|
month: grammar.hebrewMonths[cap.shift()],
|
|
year: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_islamic (o) {
|
|
let cap;
|
|
if ((cap = grammar.islamic.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'islamic',
|
|
day: utils.toNumber(cap.shift()),
|
|
month: grammar.islamicMonths[cap.shift()],
|
|
year: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_jalaali (o) {
|
|
let cap;
|
|
if ((cap = grammar.jalaali.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'jalaali',
|
|
day: utils.toNumber(cap.shift()),
|
|
month: grammar.jalaaliMonths[cap.shift()],
|
|
year: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_chineseSolar (o) {
|
|
let cap;
|
|
if ((cap = grammar.chineseSolar.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: cap.shift(),
|
|
cycle: utils.toNumber(cap.shift()),
|
|
year: utils.toNumber(cap.shift()),
|
|
solarterm: utils.toNumber(cap.shift()),
|
|
day: utils.toNumber(cap.shift()),
|
|
timezone: cap.shift()
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_chineseLunar (o) {
|
|
let cap;
|
|
if ((cap = grammar.chineseLunar.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: cap.shift(),
|
|
cycle: utils.toNumber(cap.shift()),
|
|
year: utils.toNumber(cap.shift()),
|
|
month: utils.toNumber(cap.shift()),
|
|
leapMonth: !!utils.toNumber(cap.shift()),
|
|
day: utils.toNumber(cap.shift()),
|
|
timezone: cap.shift()
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_bengaliRevised (o) {
|
|
let cap;
|
|
if ((cap = grammar.bengaliRevised.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: cap.shift(),
|
|
year: utils.toNumber(cap.shift()),
|
|
month: utils.toNumber(cap.shift()),
|
|
day: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_dateMonth (o) {
|
|
let cap;
|
|
if ((cap = grammar.dateMonth.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
fn: 'gregorian',
|
|
day: 1,
|
|
month: grammar.months[cap.shift()],
|
|
year: undefined
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_space (o) {
|
|
let cap;
|
|
if ((cap = grammar.space.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleSameDay (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_same_day.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleModifier (o) {
|
|
let cap;
|
|
if ((cap = grammar.modifier.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
modifier: cap.shift()
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleTime (o) {
|
|
let cap;
|
|
if ((cap = grammar.time.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'time',
|
|
hour: utils.toNumber(cap.shift()) || 0,
|
|
minute: utils.toNumber(cap.shift()) || 0
|
|
};
|
|
res.hour = res.hour || utils.toNumber(cap.shift()) || 0;
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleDuration (o) {
|
|
let cap;
|
|
if ((cap = grammar.duration.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const tmp = {
|
|
days: utils.toNumber(cap.shift()) || 0,
|
|
hours: utils.toNumber(cap.shift()) || 0,
|
|
minutes: utils.toNumber(cap.shift()) || 0
|
|
};
|
|
const res = {
|
|
rule: 'duration',
|
|
// duration is calculated in hours
|
|
duration: (tmp.days * 24) + tmp.hours + (tmp.minutes / 60)
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleDateIfThen (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_date_if_then.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'dateIfThen',
|
|
if: (cap.shift()).split(/(?:,\s?)/).map(lowerCaseWeekday),
|
|
direction: cap.shift(),
|
|
then: lowerCaseWeekday(cap.shift())
|
|
};
|
|
// create a sub-parser to only check for time, duration
|
|
const p = new Parser(['_ruleTime', '_ruleDuration']);
|
|
p.parse(o.str);
|
|
if (p.tokens.length) {
|
|
res.rules = p.tokens;
|
|
}
|
|
o.str = ' ' + p.setup.str; // ' ' required such that the _tokenize function finalizes the loop
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleWeekday (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_weekday.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'weekday',
|
|
not: !!cap.shift(),
|
|
if: (cap.shift()).split(/(?:,\s?)/).map(lowerCaseWeekday)
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleDateDir (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_day_dir_date.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'dateDir',
|
|
count: utils.toNumber(cap.shift()) || 1,
|
|
weekday: lowerCaseWeekday(cap.shift()),
|
|
direction: cap.shift()
|
|
};
|
|
if (res.direction === 'in') {
|
|
res.direction = 'after';
|
|
}
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleYear (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_year.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'year',
|
|
cardinality: cap.shift(),
|
|
every: utils.toNumber(cap.shift()),
|
|
since: utils.toNumber(cap.shift())
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleIfHoliday (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_if_holiday.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'ruleIfHoliday',
|
|
type: cap.shift(),
|
|
count: utils.toNumber(cap.shift()) || 1,
|
|
direction: cap.shift(),
|
|
weekday: lowerCaseWeekday(cap.shift()),
|
|
omit: (cap.shift() || '').split(/(?:,\s?)/).map(lowerCaseWeekdayWithoutDay).filter(Boolean)
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleBridge (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_bridge.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'bridge',
|
|
type: cap.shift()
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleActiveFrom (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_active_from.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'activeFrom',
|
|
year: utils.toNumber(cap.shift()),
|
|
month: utils.toNumber(cap.shift()) || 1,
|
|
day: utils.toNumber(cap.shift()) || 1
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
|
|
_ruleActiveTo (o) {
|
|
let cap;
|
|
if ((cap = grammar.rule_active_to.exec(o.str))) {
|
|
this._shorten(o, cap[0]);
|
|
cap.shift();
|
|
const res = {
|
|
rule: 'activeTo',
|
|
year: utils.toNumber(cap.shift()),
|
|
month: utils.toNumber(cap.shift()) || 1,
|
|
day: utils.toNumber(cap.shift()) || 1
|
|
};
|
|
this.tokens.push(res);
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Parser;
|