Merge pull request #21681 from Snuffleupagus/scripting-Util-getOrInsertComputed

Add helper methods for populating `dateActionsCache`/`scandCache` in the `src/scripting_api/util.js` file
This commit is contained in:
Tim van der Meij 2026-08-06 22:07:50 +02:00 committed by GitHub
commit bb40503a41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -17,13 +17,15 @@
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;
constructor(data) { #scandCache = null;
super(data);
this._scandCache = new Map(); #months = [
this._months = [
"January", "January",
"February", "February",
"March", "March",
@ -37,7 +39,8 @@ class Util extends PDFObject {
"November", "November",
"December", "December",
]; ];
this._days = [
#days = [
"Sunday", "Sunday",
"Monday", "Monday",
"Tuesday", "Tuesday",
@ -46,8 +49,13 @@ class Util extends PDFObject {
"Friday", "Friday",
"Saturday", "Saturday",
]; ];
this.MILLISECONDS_IN_DAY = 86400000;
this.MILLISECONDS_IN_WEEK = 604800000; MILLISECONDS_IN_DAY = 86400000;
MILLISECONDS_IN_WEEK = 604800000;
constructor(data) {
super(data);
// 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,16 +345,12 @@ class Util extends PDFObject {
return buf.join(""); return buf.join("");
} }
#tryToGuessDate(cFormat, cDate) { #createDateActions(cFormat) {
// We use the format to know the order of day, month, year, ... const actions = [];
let actions = (this.#dateActionsCache ||= new Map()).get(cFormat);
if (!actions) {
actions = [];
this.#dateActionsCache.set(cFormat, actions);
cFormat.replaceAll( cFormat.replaceAll(
/(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g, /(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g,
function (_match, d, m, y, H, M, s) { function (_, d, m, y, H, M, s) {
if (d) { if (d) {
actions.push((n, data) => { actions.push((n, data) => {
if (n >= 1 && n <= 31) { if (n >= 1 && n <= 31) {
@ -401,8 +405,18 @@ class Util extends PDFObject {
return ""; return "";
} }
); );
return actions;
} }
#tryToGuessDate(cFormat, cDate) {
// We use the format to know the order of day, month, year, ...
const actions = (this.#dateActionsCache ??= new Map()).getOrInsertComputed(
cFormat,
this.#createDateActionsBound
);
const number = /\d+/g; const number = /\d+/g;
let i = 0; let i = 0;
let array; let array;
@ -442,27 +456,9 @@ class Util extends PDFObject {
return this._scand(cFormat, cDate); return this._scand(cFormat, cDate);
} }
_scand(cFormat, cDate, strict = false) { #createScandData(cFormat) {
if (typeof cDate !== "string") { const months = this.#months,
return new Date(cDate); days = this.#days;
}
if (cDate === "") {
return new Date();
}
switch (cFormat) {
case 0:
return this.scand("D:yyyymmddHHMMss", cDate);
case 1:
return this.scand("yyyy.mm.dd HH:MM:ss", cDate);
case 2:
return this.scand("m/d/yy h:MM:ss tt", cDate);
}
if (!this._scandCache.has(cFormat)) {
const months = this._months;
const days = this._days;
const handlers = { const handlers = {
mmmm: { mmmm: {
@ -596,9 +592,7 @@ class Util extends PDFObject {
/(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;
const actions = []; const actions = [];
const re = escapedFormat.replaceAll( const re = escapedFormat.replaceAll(patterns, function (_, patternElement) {
patterns,
function (match, patternElement) {
const { pattern, action } = handlers[patternElement]; const { pattern, action } = handlers[patternElement];
actions.push(action); actions.push(action);
// If the format is "Hm", then /\d{1,2}\d{1,2}/ is ambiguous so we use // If the format is "Hm", then /\d{1,2}\d{1,2}/ is ambiguous so we use
@ -606,15 +600,32 @@ class Util extends PDFObject {
return pattern.includes(",") return pattern.includes(",")
? `(?=${pattern})\\${actions.length}` ? `(?=${pattern})\\${actions.length}`
: pattern; : pattern;
} });
);
this._scandCache.set(cFormat, [re, actions]); return [new RegExp(`^${re}$`, "g"), actions];
} }
const [re, actions] = this._scandCache.get(cFormat); _scand(cFormat, cDate, strict = false) {
if (typeof cDate !== "string") {
return new Date(cDate);
}
if (cDate === "") {
return new Date();
}
const matches = new RegExp(`^${re}$`, "g").exec(cDate); switch (cFormat) {
case 0:
return this.scand("D:yyyymmddHHMMss", cDate);
case 1:
return this.scand("yyyy.mm.dd HH:MM:ss", cDate);
case 2:
return this.scand("m/d/yy h:MM:ss tt", cDate);
}
const [regex, actions] = (this.#scandCache ??=
new Map()).getOrInsertComputed(cFormat, this.#createScandDataBound);
const matches = regex.exec(cDate);
if (!matches || matches.length !== actions.length + 1) { if (!matches || matches.length !== actions.length + 1) {
return strict ? null : this.#tryToGuessDate(cFormat, cDate); return strict ? null : this.#tryToGuessDate(cFormat, cDate);
} }