diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/calendar/extract/CalExtractParserService.jsm | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/calendar/extract/CalExtractParserService.jsm')
-rw-r--r-- | comm/calendar/extract/CalExtractParserService.jsm | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/comm/calendar/extract/CalExtractParserService.jsm b/comm/calendar/extract/CalExtractParserService.jsm new file mode 100644 index 0000000000..aa1ca38e1f --- /dev/null +++ b/comm/calendar/extract/CalExtractParserService.jsm @@ -0,0 +1,308 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["CalExtractParserService"]; + +const { CalExtractParser } = ChromeUtils.import( + "resource:///modules/calendar/extract/CalExtractParser.jsm" +); + +const defaultRules = [ + [ + // Start clean up patterns. + + // remove last line preceding quoted message and first line of the quote + [/^(\r?\n[^>].*\r?\n>+.*$)/, ""], + + // remove the rest of quoted content + [/^(>+.*$)/, ""], + + // urls often contain dates dates that can confuse extraction + [/^(https?:\/\/[^\s]+\s)/, ""], + [/^(www\.[^\s]+\s)/, ""], + + // remove phone numbers + [/^(\d-\d\d\d-\d\d\d-\d\d\d\d)/, ""], + + // remove standard signature + [/^(\r?\n-- \r?\n[\S\s]+$)/, ""], + + // XXX remove timezone info, for now + [/^(gmt[+-]\d{2}:\d{2})/i, ""], + + // End clean up patterns. + + [/^meet\b/i, "MEET"], + [/^(we will|we'll|we)\b/i, "WE"], + + // Meridiem + [/^(a[.]?m[.]?)/i, "AM"], + [/^(p[.]?m[.]?)/i, "PM"], + + [/^(hours|hour|hrs|hr)\b/i, "HOURS"], + [/^(minutes|min|mins)\b/i, "MINUTES"], + [/^(days|day)\b/i, "DAYS"], + + // Words commonly used when specifying begin/end time or duration. + [/^at\b/i, "AT"], + [/^until\b/i, "UNTIL"], + [/^for\b/i, "FOR"], + + // Units of time + [/^(((0|1)?[0-9])|(2[0-4]))\b/, "HOUR_VALUE"], + + [/^\d+\b/, "NUMBER"], + + // Any text we don't know the meaning of. + [/^\S+/, "TEXT"], + + // Whitespace + [/^\s+/, ""], + ], + [ + { + name: "event-guess", + patterns: ["subject", "meet", "start-time", "text*", "end-time"], + action: ([, , startTime, , endTime]) => ({ + type: "event-guess", + startTime, + endTime, + priority: 0, + }), + }, + { + name: "event-guess", + patterns: ["subject", "meet", "start-time", "text*", "duration-time"], + action: ([, , startTime, , endTime]) => ({ + type: "event-guess", + startTime, + endTime, + priority: 0, + }), + }, + { + name: "subject", + patterns: ["WE"], + action: ([subject]) => ({ + type: "subject", + subject: subject.text, + }), + }, + { + name: "start-time", + patterns: ["start-time-prefix", "meridiem-time"], + action: ([, time]) => time, + }, + { + name: "start-time-prefix", + patterns: ["AT"], + action: ([prefix]) => prefix.text, + }, + { + name: "end-time", + patterns: ["end-time-prefix", "meridiem-time"], + action: ([, time]) => time, + }, + { + name: "end-time-prefix", + patterns: ["UNTIL"], + action: ([prefix]) => prefix.text, + }, + { + name: "meridiem-time", + patterns: ["HOUR_VALUE", "meridiem"], + action: ([hour, meridiem]) => ({ + type: "meridiem-time", + hour: Number(hour.text), + meridiem, + }), + }, + { + name: "meridiem", + patterns: ["AM"], + action: () => "am", + }, + { + name: "meridiem", + patterns: ["PM"], + action: () => "pm", + }, + + { + name: "duration-time", + patterns: ["duration-prefix", "duration"], + action: ([, duration]) => ({ + type: "duration-time", + duration, + }), + }, + { + name: "duration-prefix", + patterns: ["FOR"], + action: ([prefix]) => prefix.text, + }, + { + name: "duration", + patterns: ["NUMBER", "MINUTES"], + action: ([value]) => Number(value.text), + }, + { + name: "duration", + patterns: ["NUMBER", "HOURS"], + action: ([value]) => Number(value.text) * 60, + }, + { + name: "duration", + patterns: ["NUMBER", "DAYS"], + action: ([value]) => Number(value.text) * 60 * 24, + }, + { + name: "meet", + patterns: ["MEET"], + action: () => "meet", + }, + { + name: "text", + patterns: ["TEXT"], + action: ([text]) => text, + }, + ], +]; + +/** + * CalExtractParserServiceContext represents the context parsing and extraction + * takes place in. It holds values used in various calculations. For example, + * the current date. + * + * @typedef {object} CalExtractParserServiceContext + * @property {Date} now - The Date to use when calculating start and relative + * times. + */ + +/** + * CalExtractParserService provides a frontend to the CalExtractService. + * It holds lexical and parse rules for multiple locales (or any string + * identifier) that can be used on demand when parsing text. + */ +class CalExtractParserService { + rules = new Map([["en-US", defaultRules]]); + + /** + * Parses and extract the most relevant event creation data based on the + * rules of the locale given. + * + * @param {string} source + * @param {CalExtractParserServiceContext} context + * @param {string} locale + */ + extract(source, ctx = { now: new Date() }, locale = "en-US") { + let rules = this.rules.get(locale); + if (!rules) { + return null; + } + + let [lex, parse] = rules; + let parser = CalExtractParser.createInstance(lex, parse); + let result = parser.parse(source).sort((a, b) => a - b)[0]; + return result && convertDurationToEndTime(populateTimes(result, ctx.now)); + } +} + +/** + * Populates the missing values of the startTime and endTime. + * + * @param {object?} guess - The result of CalExtractParserService.extract(). + * @param {Date} now - A Date object representing the contextual date and + * time. + * + * @returns {object} The result with the populated startTime and endTime. + */ +function populateTimes(guess, now) { + return populateTime(populateTime(guess, now, "startTime"), now, "endTime"); +} + +/** + * Populates the missing values of the specified time property based on the Date + * provided. + * + * @param {object?} guess + * @param {Date} now + * @param {string} prop + * + * @returns {object} + */ +function populateTime(guess, now, prop) { + let time = guess[prop]; + + if (!time) { + return guess; + } + if (time.hour && time.meridiem) { + time.hour = normalizeHour(time.hour, time.meridiem); + } + + time.year = time.year || now.getFullYear(); + time.month = time.month || now.getMonth() + 1; + time.day = time.day || now.getDay(); + time.hour = time.hour || now.getHours(); + time.minute = time.minute || now.getMinutes(); + return guess; +} + +/** + * Coverts an hour using the Meridiem to a 24 hour value. + * + * @param {number} hour - The hour value. + * @param {string} meridiem - "am" or "pm" + * + * @returns {number} + */ +function normalizeHour(hour, meridiem) { + if (meridiem == "am" && hour == 12) { + return hour - 12; + } else if (meridiem == "pm" && hour != 12) { + return hour + 12; + } + + let dayStart = Services.prefs.getIntPref("calendar.view.daystarthour", 6); + if (hour < dayStart && hour <= 11) { + return hour + 12; + } + + return hour; +} + +/** + * Takes care of converting an end duration to an actual time relative to the + * start time detected (if any). + * + * @param {object} guess - Results from CalExtractParserService#extract() + * + * @returns {object} The result with the endTime property expanded. + */ +function convertDurationToEndTime(guess) { + if (guess.startTime && guess.endTime && guess.endTime.type == "duration-time") { + let startTime = guess.startTime; + let duration = guess.endTime.duration; + if (duration != 0) { + let startDate = new Date(startTime.year, startTime.month - 1, startTime.day); + if ("hour" in startTime) { + startDate.setHours(startTime.hour); + startDate.setMinutes(startTime.minute); + } + + let endDate = new Date(startDate.getTime() + duration * 60 * 1000); + let endTime = { type: "date-time" }; + endTime.year = endDate.getFullYear(); + endTime.month = endDate.getMonth() + 1; + endTime.day = endDate.getDate(); + if (endDate.getHours() != 0 || endDate.getMinutes() != 0) { + endTime.hour = endDate.getHours(); + endTime.minute = endDate.getMinutes(); + } + guess.endTime = endTime; + } + } + return guess; +} |