1fc09f0235
Note that the `replaceAll` method still requires that a *global* regular expression is used, however by using this method it's immediately obvious when looking at the code that all occurrences will be replaced; please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll#parameters Please find additional details at https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-replace-all.md
588 lines
14 KiB
JavaScript
588 lines
14 KiB
JavaScript
/* Copyright 2020 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { PDFObject } from "./pdf_object.js";
|
|
|
|
class Util extends PDFObject {
|
|
constructor(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
|
|
this._externalCall = data.externalCall;
|
|
}
|
|
|
|
printf(...args) {
|
|
if (args.length === 0) {
|
|
throw new Error("Invalid number of params in printf");
|
|
}
|
|
|
|
if (typeof args[0] !== "string") {
|
|
throw new TypeError("First argument of printf must be a string");
|
|
}
|
|
|
|
const pattern = /%(,[0-4])?([+ 0#]+)?(\d+)?(\.\d+)?(.)/g;
|
|
const PLUS = 1;
|
|
const SPACE = 2;
|
|
const ZERO = 4;
|
|
const HASH = 8;
|
|
let i = 0;
|
|
return args[0].replaceAll(
|
|
pattern,
|
|
function (match, nDecSep, cFlags, nWidth, nPrecision, cConvChar) {
|
|
// cConvChar must be one of d, f, s, x
|
|
if (
|
|
cConvChar !== "d" &&
|
|
cConvChar !== "f" &&
|
|
cConvChar !== "s" &&
|
|
cConvChar !== "x"
|
|
) {
|
|
const buf = ["%"];
|
|
for (const str of [nDecSep, cFlags, nWidth, nPrecision, cConvChar]) {
|
|
if (str) {
|
|
buf.push(str);
|
|
}
|
|
}
|
|
return buf.join("");
|
|
}
|
|
|
|
i++;
|
|
if (i === args.length) {
|
|
throw new Error("Not enough arguments in printf");
|
|
}
|
|
const arg = args[i];
|
|
|
|
if (cConvChar === "s") {
|
|
return arg.toString();
|
|
}
|
|
|
|
let flags = 0;
|
|
if (cFlags) {
|
|
for (const flag of cFlags) {
|
|
switch (flag) {
|
|
case "+":
|
|
flags |= PLUS;
|
|
break;
|
|
case " ":
|
|
flags |= SPACE;
|
|
break;
|
|
case "0":
|
|
flags |= ZERO;
|
|
break;
|
|
case "#":
|
|
flags |= HASH;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
cFlags = flags;
|
|
|
|
if (nWidth) {
|
|
nWidth = parseInt(nWidth);
|
|
}
|
|
|
|
let intPart = Math.trunc(arg);
|
|
|
|
if (cConvChar === "x") {
|
|
let hex = Math.abs(intPart).toString(16).toUpperCase();
|
|
if (nWidth !== undefined) {
|
|
hex = hex.padStart(nWidth, cFlags & ZERO ? "0" : " ");
|
|
}
|
|
if (cFlags & HASH) {
|
|
hex = `0x${hex}`;
|
|
}
|
|
return hex;
|
|
}
|
|
|
|
if (nPrecision) {
|
|
nPrecision = parseInt(nPrecision.substring(1));
|
|
}
|
|
|
|
nDecSep = nDecSep ? nDecSep.substring(1) : "0";
|
|
const separators = {
|
|
0: [",", "."],
|
|
1: ["", "."],
|
|
2: [".", ","],
|
|
3: ["", ","],
|
|
4: ["'", "."],
|
|
};
|
|
const [thousandSep, decimalSep] = separators[nDecSep];
|
|
|
|
let decPart = "";
|
|
if (cConvChar === "f") {
|
|
if (nPrecision !== undefined) {
|
|
decPart = Math.abs(arg - intPart).toFixed(nPrecision);
|
|
} else {
|
|
decPart = Math.abs(arg - intPart).toString();
|
|
}
|
|
if (decPart.length > 2) {
|
|
decPart = `${decimalSep}${decPart.substring(2)}`;
|
|
} else {
|
|
if (decPart === "1") {
|
|
intPart += Math.sign(arg);
|
|
}
|
|
decPart = cFlags & HASH ? "." : "";
|
|
}
|
|
}
|
|
|
|
let sign = "";
|
|
if (intPart < 0) {
|
|
sign = "-";
|
|
intPart = -intPart;
|
|
} else if (cFlags & PLUS) {
|
|
sign = "+";
|
|
} else if (cFlags & SPACE) {
|
|
sign = " ";
|
|
}
|
|
|
|
if (thousandSep && intPart >= 1000) {
|
|
const buf = [];
|
|
while (true) {
|
|
buf.push((intPart % 1000).toString().padStart(3, "0"));
|
|
intPart = Math.trunc(intPart / 1000);
|
|
if (intPart < 1000) {
|
|
buf.push(intPart.toString());
|
|
break;
|
|
}
|
|
}
|
|
intPart = buf.reverse().join(thousandSep);
|
|
} else {
|
|
intPart = intPart.toString();
|
|
}
|
|
|
|
let n = `${intPart}${decPart}`;
|
|
if (nWidth !== undefined) {
|
|
n = n.padStart(nWidth - sign.length, cFlags & ZERO ? "0" : " ");
|
|
}
|
|
|
|
return `${sign}${n}`;
|
|
}
|
|
);
|
|
}
|
|
|
|
iconStreamFromIcon() {
|
|
/* Not implemented */
|
|
}
|
|
|
|
printd(cFormat, oDate) {
|
|
switch (cFormat) {
|
|
case 0:
|
|
return this.printd("D:yyyymmddHHMMss", oDate);
|
|
case 1:
|
|
return this.printd("yyyy.mm.dd HH:MM:ss", oDate);
|
|
case 2:
|
|
return this.printd("m/d/yy h:MM:ss tt", oDate);
|
|
}
|
|
|
|
const handlers = {
|
|
mmmm: data => {
|
|
return this._months[data.month];
|
|
},
|
|
mmm: data => {
|
|
return this._months[data.month].substring(0, 3);
|
|
},
|
|
mm: data => {
|
|
return (data.month + 1).toString().padStart(2, "0");
|
|
},
|
|
m: data => {
|
|
return (data.month + 1).toString();
|
|
},
|
|
dddd: data => {
|
|
return this._days[data.dayOfWeek];
|
|
},
|
|
ddd: data => {
|
|
return this._days[data.dayOfWeek].substring(0, 3);
|
|
},
|
|
dd: data => {
|
|
return data.day.toString().padStart(2, "0");
|
|
},
|
|
d: data => {
|
|
return data.day.toString();
|
|
},
|
|
yyyy: data => {
|
|
return data.year.toString();
|
|
},
|
|
yy: data => {
|
|
return (data.year % 100).toString().padStart(2, "0");
|
|
},
|
|
HH: data => {
|
|
return data.hours.toString().padStart(2, "0");
|
|
},
|
|
H: data => {
|
|
return data.hours.toString();
|
|
},
|
|
hh: data => {
|
|
return (1 + ((data.hours + 11) % 12)).toString().padStart(2, "0");
|
|
},
|
|
h: data => {
|
|
return (1 + ((data.hours + 11) % 12)).toString();
|
|
},
|
|
MM: data => {
|
|
return data.minutes.toString().padStart(2, "0");
|
|
},
|
|
M: data => {
|
|
return data.minutes.toString();
|
|
},
|
|
ss: data => {
|
|
return data.seconds.toString().padStart(2, "0");
|
|
},
|
|
s: data => {
|
|
return data.seconds.toString();
|
|
},
|
|
tt: data => {
|
|
return data.hours < 12 ? "am" : "pm";
|
|
},
|
|
t: data => {
|
|
return data.hours < 12 ? "a" : "p";
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
year: oDate.getFullYear(),
|
|
month: oDate.getMonth(),
|
|
day: oDate.getDate(),
|
|
dayOfWeek: oDate.getDay(),
|
|
hours: oDate.getHours(),
|
|
minutes: oDate.getMinutes(),
|
|
seconds: oDate.getSeconds(),
|
|
};
|
|
|
|
const patterns =
|
|
/(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) {
|
|
if (pattern in handlers) {
|
|
return handlers[pattern](data);
|
|
}
|
|
return pattern.charCodeAt(1);
|
|
});
|
|
}
|
|
|
|
printx(cFormat, cSource) {
|
|
// case
|
|
cSource = (cSource ?? "").toString();
|
|
const handlers = [x => x, x => x.toUpperCase(), x => x.toLowerCase()];
|
|
const buf = [];
|
|
let i = 0;
|
|
const ii = cSource.length;
|
|
let currCase = handlers[0];
|
|
let escaped = false;
|
|
|
|
for (const command of cFormat) {
|
|
if (escaped) {
|
|
buf.push(command);
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
if (i >= ii) {
|
|
break;
|
|
}
|
|
switch (command) {
|
|
case "?":
|
|
buf.push(currCase(cSource.charAt(i++)));
|
|
break;
|
|
case "X":
|
|
while (i < ii) {
|
|
const char = cSource.charAt(i++);
|
|
if (
|
|
("a" <= char && char <= "z") ||
|
|
("A" <= char && char <= "Z") ||
|
|
("0" <= char && char <= "9")
|
|
) {
|
|
buf.push(currCase(char));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "A":
|
|
while (i < ii) {
|
|
const char = cSource.charAt(i++);
|
|
if (("a" <= char && char <= "z") || ("A" <= char && char <= "Z")) {
|
|
buf.push(currCase(char));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "9":
|
|
while (i < ii) {
|
|
const char = cSource.charAt(i++);
|
|
if ("0" <= char && char <= "9") {
|
|
buf.push(char);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "*":
|
|
while (i < ii) {
|
|
buf.push(currCase(cSource.charAt(i++)));
|
|
}
|
|
break;
|
|
case "\\":
|
|
escaped = true;
|
|
break;
|
|
case ">":
|
|
currCase = handlers[1];
|
|
break;
|
|
case "<":
|
|
currCase = handlers[2];
|
|
break;
|
|
case "=":
|
|
currCase = handlers[0];
|
|
break;
|
|
default:
|
|
buf.push(command);
|
|
}
|
|
}
|
|
|
|
return buf.join("");
|
|
}
|
|
|
|
scand(cFormat, cDate) {
|
|
if (typeof cDate !== "string") {
|
|
return new Date(cDate);
|
|
}
|
|
|
|
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 = {
|
|
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);
|
|
return pattern;
|
|
}
|
|
);
|
|
|
|
this._scandCache.set(cFormat, [re, actions]);
|
|
}
|
|
|
|
const [re, actions] = this._scandCache.get(cFormat);
|
|
|
|
const matches = new RegExp(`^${re}$`, "g").exec(cDate);
|
|
if (!matches || matches.length !== actions.length + 1) {
|
|
return null;
|
|
}
|
|
|
|
const data = {
|
|
year: 2000,
|
|
month: 0,
|
|
day: 1,
|
|
hours: 0,
|
|
minutes: 0,
|
|
seconds: 0,
|
|
am: null,
|
|
};
|
|
actions.forEach((action, i) => action(matches[i + 1], data));
|
|
if (data.am !== null) {
|
|
data.hours = (data.hours % 12) + (data.am ? 0 : 12);
|
|
}
|
|
|
|
return new Date(
|
|
data.year,
|
|
data.month,
|
|
data.day,
|
|
data.hours,
|
|
data.minutes,
|
|
data.seconds
|
|
);
|
|
}
|
|
|
|
spansToXML() {
|
|
/* Not implemented */
|
|
}
|
|
|
|
stringFromStream() {
|
|
/* Not implemented */
|
|
}
|
|
|
|
xmlToSpans() {
|
|
/* Not implemented */
|
|
}
|
|
}
|
|
|
|
export { Util };
|