Add helper methods for populating dateActionsCache/scandCache in the src/scripting_api/util.js file

Rather than inlining all of that code, by moving it into separate helper methods we can utilize `Map.prototype.getOrInsertComputed()` instead.

Also, make a couple of the existing class fields actually private.
This commit is contained in:
Jonas Jenwald 2026-08-01 16:21:37 +02:00
parent 5f181fd8b0
commit f1f1782f38

View File

@ -17,38 +17,46 @@
import { PDFObject } from "./pdf_object.js"; import { PDFObject } from "./pdf_object.js";
class Util extends PDFObject { class Util extends PDFObject {
#createDateActionsBound = this.#createDateActions.bind(this);
#createScandDataBound = this.#createScandData.bind(this);
#dateActionsCache = null; #dateActionsCache = null;
#scandCache = null;
#months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
#days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
MILLISECONDS_IN_DAY = 86400000;
MILLISECONDS_IN_WEEK = 604800000;
constructor(data) { constructor(data) {
super(data); super(data);
this._scandCache = new Map();
this._months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
this._days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
this.MILLISECONDS_IN_DAY = 86400000;
this.MILLISECONDS_IN_WEEK = 604800000;
// used with crackURL // used with crackURL
this._externalCall = data.externalCall; this._externalCall = data.externalCall;
} }
@ -71,7 +79,7 @@ class Util extends PDFObject {
let i = 0; let i = 0;
return args[0].replaceAll( return args[0].replaceAll(
pattern, pattern,
function (match, nDecSep, cFlags, nWidth, nPrecision, cConvChar) { function (_, nDecSep, cFlags, nWidth, nPrecision, cConvChar) {
// cConvChar must be one of d, f, s, x // cConvChar must be one of d, f, s, x
if ( if (
cConvChar !== "d" && cConvChar !== "d" &&
@ -217,12 +225,12 @@ class Util extends PDFObject {
} }
const handlers = { const handlers = {
mmmm: data => this._months[data.month], mmmm: data => this.#months[data.month],
mmm: data => this._months[data.month].substring(0, 3), mmm: data => this.#months[data.month].substring(0, 3),
mm: data => (data.month + 1).toString().padStart(2, "0"), mm: data => (data.month + 1).toString().padStart(2, "0"),
m: data => (data.month + 1).toString(), m: data => (data.month + 1).toString(),
dddd: data => this._days[data.dayOfWeek], dddd: data => this.#days[data.dayOfWeek],
ddd: data => this._days[data.dayOfWeek].substring(0, 3), ddd: data => this.#days[data.dayOfWeek].substring(0, 3),
dd: data => data.day.toString().padStart(2, "0"), dd: data => data.day.toString().padStart(2, "0"),
d: data => data.day.toString(), d: data => data.day.toString(),
yyyy: data => data.year.toString().padStart(4, "0"), yyyy: data => data.year.toString().padStart(4, "0"),
@ -251,7 +259,7 @@ class Util extends PDFObject {
const patterns = const patterns =
/(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g; /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g;
return cFormat.replaceAll(patterns, function (match, pattern) { return cFormat.replaceAll(patterns, function (_, pattern) {
return pattern in handlers return pattern in handlers
? handlers[pattern](data) ? handlers[pattern](data)
: pattern.charCodeAt(1); : pattern.charCodeAt(1);
@ -337,71 +345,77 @@ class Util extends PDFObject {
return buf.join(""); return buf.join("");
} }
#createDateActions(cFormat) {
const actions = [];
cFormat.replaceAll(
/(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g,
function (_, d, m, y, H, M, s) {
if (d) {
actions.push((n, data) => {
if (n >= 1 && n <= 31) {
data.day = n;
return true;
}
return false;
});
} else if (m) {
actions.push((n, data) => {
if (n >= 1 && n <= 12) {
data.month = n - 1;
return true;
}
return false;
});
} else if (y) {
actions.push((n, data) => {
if (n < 50) {
n += 2000;
} else if (n < 100) {
n += 1900;
}
data.year = n;
return true;
});
} else if (H) {
actions.push((n, data) => {
if (n >= 0 && n <= 23) {
data.hours = n;
return true;
}
return false;
});
} else if (M) {
actions.push((n, data) => {
if (n >= 0 && n <= 59) {
data.minutes = n;
return true;
}
return false;
});
} else if (s) {
actions.push((n, data) => {
if (n >= 0 && n <= 59) {
data.seconds = n;
return true;
}
return false;
});
}
return "";
}
);
return actions;
}
#tryToGuessDate(cFormat, cDate) { #tryToGuessDate(cFormat, cDate) {
// We use the format to know the order of day, month, year, ... // We use the format to know the order of day, month, year, ...
let actions = (this.#dateActionsCache ||= new Map()).get(cFormat); const actions = (this.#dateActionsCache ??= new Map()).getOrInsertComputed(
if (!actions) { cFormat,
actions = []; this.#createDateActionsBound
this.#dateActionsCache.set(cFormat, actions); );
cFormat.replaceAll(
/(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g,
function (_match, d, m, y, H, M, s) {
if (d) {
actions.push((n, data) => {
if (n >= 1 && n <= 31) {
data.day = n;
return true;
}
return false;
});
} else if (m) {
actions.push((n, data) => {
if (n >= 1 && n <= 12) {
data.month = n - 1;
return true;
}
return false;
});
} else if (y) {
actions.push((n, data) => {
if (n < 50) {
n += 2000;
} else if (n < 100) {
n += 1900;
}
data.year = n;
return true;
});
} else if (H) {
actions.push((n, data) => {
if (n >= 0 && n <= 23) {
data.hours = n;
return true;
}
return false;
});
} else if (M) {
actions.push((n, data) => {
if (n >= 0 && n <= 59) {
data.minutes = n;
return true;
}
return false;
});
} else if (s) {
actions.push((n, data) => {
if (n >= 0 && n <= 59) {
data.seconds = n;
return true;
}
return false;
});
}
return "";
}
);
}
const number = /\d+/g; const number = /\d+/g;
let i = 0; let i = 0;
@ -442,11 +456,159 @@ class Util extends PDFObject {
return this._scand(cFormat, cDate); return this._scand(cFormat, cDate);
} }
#createScandData(cFormat) {
const months = this.#months,
days = this.#days;
const handlers = {
mmmm: {
pattern: `(${months.join("|")})`,
action: (value, data) => {
data.month = months.indexOf(value);
},
},
mmm: {
pattern: `(${months.map(month => month.substring(0, 3)).join("|")})`,
action: (value, data) => {
data.month = months.findIndex(
month => month.substring(0, 3) === value
);
},
},
mm: {
pattern: `(\\d{2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
m: {
pattern: `(\\d{1,2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
dddd: {
pattern: `(${days.join("|")})`,
action: (value, data) => {
data.day = days.indexOf(value);
},
},
ddd: {
pattern: `(${days.map(day => day.substring(0, 3)).join("|")})`,
action: (value, data) => {
data.day = days.findIndex(day => day.substring(0, 3) === value);
},
},
dd: {
pattern: "(\\d{2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
d: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
yyyy: {
pattern: "(\\d{4})",
action: (value, data) => {
data.year = parseInt(value);
},
},
yy: {
pattern: "(\\d{2})",
action: (value, data) => {
data.year = 2000 + parseInt(value);
},
},
HH: {
pattern: "(\\d{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
H: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
hh: {
pattern: "(\\d{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
h: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
MM: {
pattern: "(\\d{2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
M: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
ss: {
pattern: "(\\d{2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
s: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
tt: {
pattern: "([aApP][mM])",
action: (value, data) => {
const char = value.charAt(0);
data.am = char === "a" || char === "A";
},
},
t: {
pattern: "([aApP])",
action: (value, data) => {
data.am = value === "a" || value === "A";
},
},
};
// escape the string
const escapedFormat = cFormat.replaceAll(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
const patterns =
/(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;
const actions = [];
const re = escapedFormat.replaceAll(patterns, function (_, patternElement) {
const { pattern, action } = handlers[patternElement];
actions.push(action);
// If the format is "Hm", then /\d{1,2}\d{1,2}/ is ambiguous so we use
// a lookahead to ensure that we match the longest possible sequence.
return pattern.includes(",")
? `(?=${pattern})\\${actions.length}`
: pattern;
});
return [re, actions];
}
_scand(cFormat, cDate, strict = false) { _scand(cFormat, cDate, strict = false) {
if (typeof cDate !== "string") { if (typeof cDate !== "string") {
return new Date(cDate); return new Date(cDate);
} }
if (cDate === "") { if (cDate === "") {
return new Date(); return new Date();
} }
@ -460,159 +622,10 @@ class Util extends PDFObject {
return this.scand("m/d/yy h:MM:ss tt", cDate); return this.scand("m/d/yy h:MM:ss tt", cDate);
} }
if (!this._scandCache.has(cFormat)) { const [re, actions] = (this.#scandCache ??= new Map()).getOrInsertComputed(
const months = this._months; cFormat,
const days = this._days; this.#createScandDataBound
);
const handlers = {
mmmm: {
pattern: `(${months.join("|")})`,
action: (value, data) => {
data.month = months.indexOf(value);
},
},
mmm: {
pattern: `(${months.map(month => month.substring(0, 3)).join("|")})`,
action: (value, data) => {
data.month = months.findIndex(
month => month.substring(0, 3) === value
);
},
},
mm: {
pattern: `(\\d{2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
m: {
pattern: `(\\d{1,2})`,
action: (value, data) => {
data.month = parseInt(value) - 1;
},
},
dddd: {
pattern: `(${days.join("|")})`,
action: (value, data) => {
data.day = days.indexOf(value);
},
},
ddd: {
pattern: `(${days.map(day => day.substring(0, 3)).join("|")})`,
action: (value, data) => {
data.day = days.findIndex(day => day.substring(0, 3) === value);
},
},
dd: {
pattern: "(\\d{2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
d: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.day = parseInt(value);
},
},
yyyy: {
pattern: "(\\d{4})",
action: (value, data) => {
data.year = parseInt(value);
},
},
yy: {
pattern: "(\\d{2})",
action: (value, data) => {
data.year = 2000 + parseInt(value);
},
},
HH: {
pattern: "(\\d{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
H: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
hh: {
pattern: "(\\d{2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
h: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.hours = parseInt(value);
},
},
MM: {
pattern: "(\\d{2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
M: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.minutes = parseInt(value);
},
},
ss: {
pattern: "(\\d{2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
s: {
pattern: "(\\d{1,2})",
action: (value, data) => {
data.seconds = parseInt(value);
},
},
tt: {
pattern: "([aApP][mM])",
action: (value, data) => {
const char = value.charAt(0);
data.am = char === "a" || char === "A";
},
},
t: {
pattern: "([aApP])",
action: (value, data) => {
data.am = value === "a" || value === "A";
},
},
};
// escape the string
const escapedFormat = cFormat.replaceAll(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
const patterns =
/(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;
const actions = [];
const re = escapedFormat.replaceAll(
patterns,
function (match, patternElement) {
const { pattern, action } = handlers[patternElement];
actions.push(action);
// If the format is "Hm", then /\d{1,2}\d{1,2}/ is ambiguous so we use
// a lookahead to ensure that we match the longest possible sequence.
return pattern.includes(",")
? `(?=${pattern})\\${actions.length}`
: pattern;
}
);
this._scandCache.set(cFormat, [re, actions]);
}
const [re, actions] = this._scandCache.get(cFormat);
const matches = new RegExp(`^${re}$`, "g").exec(cDate); const matches = new RegExp(`^${re}$`, "g").exec(cDate);
if (!matches || matches.length !== actions.length + 1) { if (!matches || matches.length !== actions.length + 1) {