[JS] Make the date parser less strict

and display the expected date formt as a tooltip.
This commit is contained in:
Calixte Denizet 2025-06-19 16:15:02 +02:00
parent 5653458b51
commit 2541d96bf5
6 changed files with 81 additions and 47 deletions

View File

@ -61,6 +61,7 @@ import {
parseAppearanceStream,
parseDefaultAppearance,
} from "./default_appearance.js";
import { DateFormats, TimeFormats } from "../shared/scripting_utils.js";
import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
import { Stream, StringStream } from "./stream.js";
import { BaseStream } from "./base_stream.js";
@ -2780,6 +2781,25 @@ class TextWidgetAnnotation extends WidgetAnnotation {
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
this.data.maxLen !== 0;
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
// Check if we have a date or time.
const {
data: { actions },
} = this;
for (const keystrokeAction of actions?.Keystroke || []) {
const m = keystrokeAction
.trim()
.match(/^AF(Date|Time)_Keystroke(?:Ex)?\(['"]?([^'"]+)['"]?\);$/);
if (m) {
let format = m[2];
const num = parseInt(format, 10);
if (!isNaN(num) && Math.floor(Math.log10(num)) + 1 === m[2].length) {
format = (m[1] === "Date" ? DateFormats : TimeFormats)[num] ?? format;
}
this.data[m[1] === "Date" ? "dateFormat" : "timeFormat"] = format;
break;
}
}
}
get hasTextContent() {

View File

@ -1293,6 +1293,10 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
element.disabled = this.data.readOnly;
element.name = this.data.fieldName;
element.tabIndex = 0;
const format = this.data.dateFormat || this.data.timeFormat;
if (format) {
element.title = format;
}
this._setRequired(element, this.data.required);

View File

@ -13,6 +13,7 @@
* limitations under the License.
*/
import { DateFormats, TimeFormats } from "../shared/scripting_utils.js";
import { GlobalConstants } from "./constants.js";
class AForm {
@ -21,23 +22,6 @@ class AForm {
this._app = app;
this._util = util;
this._color = color;
this._dateFormats = [
"m/d",
"m/d/yy",
"mm/dd/yy",
"mm/yy",
"d-mmm",
"d-mmm-yy",
"dd-mmm-yy",
"yy-mm-dd",
"mmm-yy",
"mmmm-yy",
"mmm d, yyyy",
"mmmm d, yyyy",
"m/d/yy h:MM tt",
"m/d/yy HH:MM",
];
this._timeFormats = ["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"];
// The e-mail address regex below originates from:
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
@ -52,17 +36,14 @@ class AForm {
return event.target ? `[ ${event.target.name} ]` : "";
}
_parseDate(cFormat, cDate, strict = false) {
_parseDate(cFormat, cDate) {
let date = null;
try {
date = this._util._scand(cFormat, cDate, strict);
date = this._util._scand(cFormat, cDate, /* strict = */ false);
} catch {}
if (date) {
return date;
}
if (strict) {
return null;
}
date = Date.parse(cDate);
return isNaN(date) ? null : new Date(date);
@ -277,9 +258,7 @@ class AForm {
}
AFDate_Format(pdf) {
if (pdf >= 0 && pdf < this._dateFormats.length) {
this.AFDate_FormatEx(this._dateFormats[pdf]);
}
this.AFDate_FormatEx(DateFormats[pdf] ?? pdf);
}
AFDate_KeystrokeEx(cFormat) {
@ -293,7 +272,7 @@ class AForm {
return;
}
if (this._parseDate(cFormat, value, /* strict = */ true) === null) {
if (this._parseDate(cFormat, value) === null) {
const invalid = GlobalConstants.IDS_INVALID_DATE;
const invalid2 = GlobalConstants.IDS_INVALID_DATE2;
const err = `${invalid} ${this._mkTargetName(
@ -305,8 +284,8 @@ class AForm {
}
AFDate_Keystroke(pdf) {
if (pdf >= 0 && pdf < this._dateFormats.length) {
this.AFDate_KeystrokeEx(this._dateFormats[pdf]);
if (pdf >= 0 && pdf < DateFormats.length) {
this.AFDate_KeystrokeEx(DateFormats[pdf]);
}
}
@ -617,9 +596,7 @@ class AForm {
}
AFTime_Format(pdf) {
if (pdf >= 0 && pdf < this._timeFormats.length) {
this.AFDate_FormatEx(this._timeFormats[pdf]);
}
this.AFDate_FormatEx(TimeFormats[pdf] ?? pdf);
}
AFTime_KeystrokeEx(cFormat) {
@ -627,8 +604,8 @@ class AForm {
}
AFTime_Keystroke(pdf) {
if (pdf >= 0 && pdf < this._timeFormats.length) {
this.AFDate_KeystrokeEx(this._timeFormats[pdf]);
if (pdf >= 0 && pdf < TimeFormats.length) {
this.AFDate_KeystrokeEx(TimeFormats[pdf]);
}
}

View File

@ -227,7 +227,7 @@ class Util extends PDFObject {
ddd: data => this._days[data.dayOfWeek].substring(0, 3),
dd: data => data.day.toString().padStart(2, "0"),
d: data => data.day.toString(),
yyyy: data => data.year.toString(),
yyyy: data => data.year.toString().padStart(4, "0"),
yy: data => (data.year % 100).toString().padStart(2, "0"),
HH: data => data.hours.toString().padStart(2, "0"),
H: data => data.hours.toString(),

View File

@ -105,4 +105,22 @@ class ColorConverters {
}
}
export { ColorConverters };
const DateFormats = [
"m/d",
"m/d/yy",
"mm/dd/yy",
"mm/yy",
"d-mmm",
"d-mmm-yy",
"dd-mmm-yy",
"yy-mm-dd",
"mmm-yy",
"mmmm-yy",
"mmm d, yyyy",
"mmmm d, yyyy",
"m/d/yy h:MM tt",
"m/d/yy HH:MM",
];
const TimeFormats = ["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"];
export { ColorConverters, DateFormats, TimeFormats };

View File

@ -1065,8 +1065,8 @@ describe("Scripting", function () {
id: refId,
value: "",
actions: {
Format: [`AFDate_FormatEx("mmddyyyy");`],
Keystroke: [`AFDate_KeystrokeEx("mmddyyyy");`],
Format: [`AFDate_FormatEx("mm.dd.yyyy");`],
Keystroke: [`AFDate_KeystrokeEx("mm.dd.yyyy");`],
},
type: "text",
},
@ -1080,7 +1080,7 @@ describe("Scripting", function () {
sandbox.createSandbox(data);
await sandbox.dispatchEventInSandbox({
id: refId,
value: "12062023",
value: "12.06.2023",
name: "Keystroke",
willCommit: true,
});
@ -1088,14 +1088,14 @@ describe("Scripting", function () {
expect(send_queue.get(refId)).toEqual({
id: refId,
siblings: null,
value: "12062023",
formattedValue: "12062023",
value: "12.06.2023",
formattedValue: "12.06.2023",
});
send_queue.delete(refId);
await sandbox.dispatchEventInSandbox({
id: refId,
value: "1206202",
value: "12.06.202",
name: "Keystroke",
willCommit: true,
});
@ -1103,16 +1103,15 @@ describe("Scripting", function () {
expect(send_queue.get(refId)).toEqual({
id: refId,
siblings: null,
value: "",
formattedValue: null,
selRange: [0, 0],
value: "12.06.202",
formattedValue: "12.06.0202",
});
send_queue.delete(refId);
sandbox.createSandbox(data);
await sandbox.dispatchEventInSandbox({
id: refId,
value: "02062023",
value: "02.06.2023",
name: "Keystroke",
willCommit: true,
});
@ -1120,8 +1119,24 @@ describe("Scripting", function () {
expect(send_queue.get(refId)).toEqual({
id: refId,
siblings: null,
value: "02062023",
formattedValue: "02062023",
value: "02.06.2023",
formattedValue: "02.06.2023",
});
send_queue.delete(refId);
sandbox.createSandbox(data);
await sandbox.dispatchEventInSandbox({
id: refId,
value: "2.6.2023",
name: "Keystroke",
willCommit: true,
});
expect(send_queue.has(refId)).toEqual(true);
expect(send_queue.get(refId)).toEqual({
id: refId,
siblings: null,
value: "2.6.2023",
formattedValue: "02.06.2023",
});
send_queue.delete(refId);
});