717 lines
22 KiB
C++
717 lines
22 KiB
C++
#include "ReminderParser.h"
|
||
|
||
#include <QDate>
|
||
#include <QRegularExpression>
|
||
#include <QStringList>
|
||
#include <QTime>
|
||
|
||
namespace
|
||
{
|
||
bool containsAny(const QString &text, const QStringList &keywords)
|
||
{
|
||
for (const QString &keyword : keywords)
|
||
{
|
||
if (text.contains(keyword))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
int chineseDigitValue(QChar value)
|
||
{
|
||
if (value == QLatin1Char('0') || value == QChar(0x3007) || value == QStringLiteral("零").at(0))
|
||
{
|
||
return 0;
|
||
}
|
||
if (value == QStringLiteral("一").at(0))
|
||
{
|
||
return 1;
|
||
}
|
||
if (value == QStringLiteral("二").at(0) || value == QStringLiteral("两").at(0))
|
||
{
|
||
return 2;
|
||
}
|
||
if (value == QStringLiteral("三").at(0))
|
||
{
|
||
return 3;
|
||
}
|
||
if (value == QStringLiteral("四").at(0))
|
||
{
|
||
return 4;
|
||
}
|
||
if (value == QStringLiteral("五").at(0))
|
||
{
|
||
return 5;
|
||
}
|
||
if (value == QStringLiteral("六").at(0))
|
||
{
|
||
return 6;
|
||
}
|
||
if (value == QStringLiteral("七").at(0))
|
||
{
|
||
return 7;
|
||
}
|
||
if (value == QStringLiteral("八").at(0))
|
||
{
|
||
return 8;
|
||
}
|
||
if (value == QStringLiteral("九").at(0))
|
||
{
|
||
return 9;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
int parseSmallInteger(QString value)
|
||
{
|
||
value = value.trimmed();
|
||
value.remove(QStringLiteral("个"));
|
||
if (value.isEmpty())
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
bool ok = false;
|
||
const int numericValue = value.toInt(&ok);
|
||
if (ok)
|
||
{
|
||
return numericValue;
|
||
}
|
||
|
||
const int tenIndex = value.indexOf(QStringLiteral("十"));
|
||
if (tenIndex >= 0)
|
||
{
|
||
const QString left = value.left(tenIndex);
|
||
const QString right = value.mid(tenIndex + 1);
|
||
const int tens = left.isEmpty() ? 1 : parseSmallInteger(left);
|
||
const int ones = right.isEmpty() ? 0 : parseSmallInteger(right);
|
||
if (tens < 0 || ones < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
return tens * 10 + ones;
|
||
}
|
||
|
||
if (value.size() == 1)
|
||
{
|
||
return chineseDigitValue(value.at(0));
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
QString cleanedText(QString text)
|
||
{
|
||
return text
|
||
.replace(QChar(0xff0c), QStringLiteral(" "))
|
||
.replace(QChar(0x3002), QStringLiteral(" "))
|
||
.replace(QChar(0xff1a), QStringLiteral(":"))
|
||
.replace(QChar(0xff1b), QStringLiteral(" "))
|
||
.trimmed();
|
||
}
|
||
|
||
int adjustedHour(const QString &period, int hour)
|
||
{
|
||
if (hour < 0)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
if (period == QStringLiteral("下午") || period == QStringLiteral("晚上"))
|
||
{
|
||
return hour < 12 ? hour + 12 : hour;
|
||
}
|
||
|
||
if (period == QStringLiteral("中午"))
|
||
{
|
||
return hour < 11 ? hour + 12 : hour;
|
||
}
|
||
|
||
if (period == QStringLiteral("凌晨") && hour == 12)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
return hour;
|
||
}
|
||
|
||
int weekdayFromText(const QString &text)
|
||
{
|
||
const QString normalized = text.trimmed();
|
||
if (normalized == QStringLiteral("一") || normalized == QStringLiteral("1"))
|
||
{
|
||
return 1;
|
||
}
|
||
if (normalized == QStringLiteral("二") || normalized == QStringLiteral("2"))
|
||
{
|
||
return 2;
|
||
}
|
||
if (normalized == QStringLiteral("三") || normalized == QStringLiteral("3"))
|
||
{
|
||
return 3;
|
||
}
|
||
if (normalized == QStringLiteral("四") || normalized == QStringLiteral("4"))
|
||
{
|
||
return 4;
|
||
}
|
||
if (normalized == QStringLiteral("五") || normalized == QStringLiteral("5"))
|
||
{
|
||
return 5;
|
||
}
|
||
if (normalized == QStringLiteral("六") || normalized == QStringLiteral("6"))
|
||
{
|
||
return 6;
|
||
}
|
||
if (normalized == QStringLiteral("日")
|
||
|| normalized == QStringLiteral("天")
|
||
|| normalized == QStringLiteral("7"))
|
||
{
|
||
return 7;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
struct ReminderDateResolution
|
||
{
|
||
QDate date;
|
||
bool explicitDate = false;
|
||
};
|
||
|
||
struct ParsedReminderTime
|
||
{
|
||
int hour = -1;
|
||
int minute = -1;
|
||
QString expression;
|
||
};
|
||
|
||
ReminderDateResolution resolveReminderDate(const QString &text, const QDate ¤tDate)
|
||
{
|
||
QRegularExpressionMatch match;
|
||
|
||
const QRegularExpression nextWeekExpression(QStringLiteral("下周\\s*([一二三四五六日天1-7])"));
|
||
match = nextWeekExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const int targetWeekday = weekdayFromText(match.captured(1));
|
||
if (targetWeekday > 0)
|
||
{
|
||
const int daysToNextMonday = 8 - currentDate.dayOfWeek();
|
||
return {currentDate.addDays(daysToNextMonday + targetWeekday - 1), true};
|
||
}
|
||
}
|
||
|
||
const QRegularExpression monthDayExpression(QStringLiteral("(\\d{1,2})\\s*月\\s*(\\d{1,2})\\s*(?:日|号)?"));
|
||
match = monthDayExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const int month = match.captured(1).toInt();
|
||
const int day = match.captured(2).toInt();
|
||
QDate date(currentDate.year(), month, day);
|
||
if (date.isValid() && date < currentDate)
|
||
{
|
||
date = date.addYears(1);
|
||
}
|
||
return {date, true};
|
||
}
|
||
|
||
const QRegularExpression slashDateExpression(QStringLiteral("(\\d{1,2})\\s*/\\s*(\\d{1,2})"));
|
||
match = slashDateExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const int month = match.captured(1).toInt();
|
||
const int day = match.captured(2).toInt();
|
||
QDate date(currentDate.year(), month, day);
|
||
if (date.isValid() && date < currentDate)
|
||
{
|
||
date = date.addYears(1);
|
||
}
|
||
return {date, true};
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("后天")))
|
||
{
|
||
return {currentDate.addDays(2), true};
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("明天")))
|
||
{
|
||
return {currentDate.addDays(1), true};
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("今天")))
|
||
{
|
||
return {currentDate, true};
|
||
}
|
||
|
||
return {currentDate, false};
|
||
}
|
||
|
||
QString removeFirst(const QString &text, const QString &part)
|
||
{
|
||
QString result = text;
|
||
const int index = result.indexOf(part);
|
||
if (index >= 0)
|
||
{
|
||
result.remove(index, part.size());
|
||
}
|
||
return result;
|
||
}
|
||
|
||
ReminderCommand invalidRecurringCommand(const QString &text, const QString &message)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, message};
|
||
}
|
||
|
||
ReminderCommand createReminderCommand(
|
||
const QString &title,
|
||
const QString &originalText,
|
||
const QDateTime &remindAt,
|
||
const ReminderRecurrence &recurrence = {})
|
||
{
|
||
ReminderCommand command;
|
||
command.type = ReminderCommandType::Create;
|
||
command.title = title;
|
||
command.originalText = originalText;
|
||
command.remindAt = remindAt;
|
||
command.recurrence = recurrence;
|
||
return command;
|
||
}
|
||
|
||
ParsedReminderTime parsedTimeFromPointMatch(const QRegularExpressionMatch &match, int periodIndex, int hourIndex, int minuteIndex)
|
||
{
|
||
const QString period = match.captured(periodIndex);
|
||
const int hour = adjustedHour(period, parseSmallInteger(match.captured(hourIndex)));
|
||
const int minute = match.captured(minuteIndex).isEmpty() ? 0 : match.captured(minuteIndex).toInt();
|
||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
||
{
|
||
return {};
|
||
}
|
||
|
||
return {hour, minute, match.captured(0)};
|
||
}
|
||
|
||
ParsedReminderTime parsedTimeFromColonMatch(const QRegularExpressionMatch &match, int hourIndex, int minuteIndex)
|
||
{
|
||
const int hour = match.captured(hourIndex).toInt();
|
||
const int minute = match.captured(minuteIndex).toInt();
|
||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
||
{
|
||
return {};
|
||
}
|
||
|
||
return {hour, minute, match.captured(0)};
|
||
}
|
||
|
||
ParsedReminderTime parseTimeExpression(const QString &text)
|
||
{
|
||
QRegularExpressionMatch match;
|
||
const QRegularExpression pointExpression(QStringLiteral("(上午|早上|下午|晚上|中午|凌晨)?\\s*([0-9]{1,2}|[一二两三四五六七八九十]+)\\s*点\\s*(?:(\\d{1,2})\\s*分?)?"));
|
||
match = pointExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
return parsedTimeFromPointMatch(match, 1, 2, 3);
|
||
}
|
||
|
||
const QRegularExpression colonExpression(QStringLiteral("([01]?\\d|2[0-3])\\s*[::]\\s*([0-5]\\d)"));
|
||
match = colonExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
return parsedTimeFromColonMatch(match, 1, 2);
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
QDateTime nextDailyOccurrence(const QDateTime &now, const QTime &time)
|
||
{
|
||
QDateTime next(now.date(), time);
|
||
if (next <= now)
|
||
{
|
||
next = next.addDays(1);
|
||
}
|
||
return next;
|
||
}
|
||
|
||
QDateTime nextWeeklyOccurrence(const QDateTime &now, int weekday, const QTime &time)
|
||
{
|
||
int daysToAdd = weekday - now.date().dayOfWeek();
|
||
QDateTime next(now.date().addDays(daysToAdd), time);
|
||
if (daysToAdd < 0 || next <= now)
|
||
{
|
||
next = next.addDays(7);
|
||
}
|
||
return next;
|
||
}
|
||
|
||
QDateTime nextMonthlyOccurrence(const QDateTime &now, int monthDay, const QTime &time)
|
||
{
|
||
if (monthDay < 1 || monthDay > 31)
|
||
{
|
||
return {};
|
||
}
|
||
|
||
QDate monthCursor(now.date().year(), now.date().month(), 1);
|
||
for (int attempt = 0; attempt < 240; ++attempt)
|
||
{
|
||
const QDate date(monthCursor.year(), monthCursor.month(), monthDay);
|
||
if (date.isValid())
|
||
{
|
||
const QDateTime next(date, time);
|
||
if (next > now)
|
||
{
|
||
return next;
|
||
}
|
||
}
|
||
monthCursor = monthCursor.addMonths(1);
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
bool containsUnsupportedRecurrence(const QString &text)
|
||
{
|
||
static const QRegularExpression intervalExpression(QStringLiteral("每\\s*([0-9]+|[一二两三四五六七八九十]+)\\s*(天|周|星期|月)"));
|
||
return containsAny(text, {
|
||
QStringLiteral("每年"),
|
||
QStringLiteral("工作日"),
|
||
QStringLiteral("重复"),
|
||
QStringLiteral("每隔"),
|
||
QStringLiteral("隔天"),
|
||
QStringLiteral("每季度"),
|
||
QStringLiteral("每季"),
|
||
QStringLiteral("每小时"),
|
||
QStringLiteral("每分钟"),
|
||
QStringLiteral("农历"),
|
||
}) || intervalExpression.match(text).hasMatch();
|
||
}
|
||
}
|
||
|
||
ReminderCommand ReminderParser::parse(const QString &text, const QDateTime &now) const
|
||
{
|
||
const QString normalized = cleanedText(text);
|
||
if (normalized.isEmpty())
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, {}, {}, {}, QStringLiteral("提醒内容为空。")};
|
||
}
|
||
|
||
if (containsAny(normalized, {QStringLiteral("提醒列表"), QStringLiteral("查看提醒"), QStringLiteral("我的提醒"), QStringLiteral("有哪些提醒")}))
|
||
{
|
||
return {ReminderCommandType::List, {}, normalized};
|
||
}
|
||
|
||
if (normalized.contains(QStringLiteral("取消提醒")) || normalized.startsWith(QStringLiteral("取消")))
|
||
{
|
||
QString query = normalized;
|
||
query.remove(QStringLiteral("取消提醒"));
|
||
if (query == normalized)
|
||
{
|
||
query.remove(QStringLiteral("取消"));
|
||
query.remove(QStringLiteral("提醒"));
|
||
}
|
||
query = query.trimmed();
|
||
if (query.isEmpty())
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, normalized, {}, {}, QStringLiteral("请说明要取消哪条提醒。")};
|
||
}
|
||
return {ReminderCommandType::Cancel, {}, normalized, {}, query};
|
||
}
|
||
|
||
return parseCreateCommand(normalized, now);
|
||
}
|
||
|
||
ReminderCommand ReminderParser::parseCreateCommand(const QString &text, const QDateTime &now) const
|
||
{
|
||
const QDate currentDate = now.date();
|
||
|
||
const ReminderCommand recurringCommand = parseRecurringCreateCommand(text, now);
|
||
if (recurringCommand.type != ReminderCommandType::Invalid || !recurringCommand.errorMessage.isEmpty())
|
||
{
|
||
return recurringCommand;
|
||
}
|
||
|
||
if (containsUnsupportedRecurrence(text))
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("暂不支持该重复提醒规则,目前支持每天、每周、每月。")};
|
||
}
|
||
|
||
const QRegularExpression relativeMinutesExpression(QStringLiteral("([0-9]+|[一二两三四五六七八九十]+)\\s*分钟后"));
|
||
QRegularExpressionMatch match = relativeMinutesExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const int minutes = parseSmallInteger(match.captured(1));
|
||
if (minutes <= 0)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("提醒时间必须晚于当前时间。")};
|
||
}
|
||
|
||
const QString timeExpression = match.captured(0);
|
||
return {
|
||
ReminderCommandType::Create,
|
||
extractTitle(text, timeExpression),
|
||
text,
|
||
now.addSecs(minutes * 60),
|
||
{},
|
||
{},
|
||
};
|
||
}
|
||
|
||
const QRegularExpression relativeOneAndHalfHourExpression(QStringLiteral("([0-9]+|[一二两三四五六七八九十]+)\\s*(?:个)?半小时后"));
|
||
match = relativeOneAndHalfHourExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const int hours = parseSmallInteger(match.captured(1));
|
||
if (hours < 0)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("没有识别到有效提醒时间。")};
|
||
}
|
||
|
||
const QString timeExpression = match.captured(0);
|
||
return {
|
||
ReminderCommandType::Create,
|
||
extractTitle(text, timeExpression),
|
||
text,
|
||
now.addSecs(hours * 60 * 60 + 30 * 60),
|
||
{},
|
||
{},
|
||
};
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("半小时后")))
|
||
{
|
||
const QString timeExpression = QStringLiteral("半小时后");
|
||
return {
|
||
ReminderCommandType::Create,
|
||
extractTitle(text, timeExpression),
|
||
text,
|
||
now.addSecs(30 * 60),
|
||
{},
|
||
{},
|
||
};
|
||
}
|
||
|
||
const QRegularExpression relativeHoursExpression(QStringLiteral("([0-9]+|[一二两三四五六七八九十]+)\\s*(?:个)?小时后"));
|
||
match = relativeHoursExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const int hours = parseSmallInteger(match.captured(1));
|
||
if (hours <= 0)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("提醒时间必须晚于当前时间。")};
|
||
}
|
||
|
||
const QString timeExpression = match.captured(0);
|
||
return {
|
||
ReminderCommandType::Create,
|
||
extractTitle(text, timeExpression),
|
||
text,
|
||
now.addSecs(hours * 60 * 60),
|
||
{},
|
||
{},
|
||
};
|
||
}
|
||
|
||
const ReminderDateResolution dateResolution = resolveReminderDate(text, currentDate);
|
||
if (!dateResolution.date.isValid())
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("没有识别到有效提醒日期。")};
|
||
}
|
||
|
||
const QRegularExpression clockExpression(QStringLiteral("(上午|早上|下午|晚上|中午|凌晨)?\\s*([0-9]{1,2}|[一二两三四五六七八九十]+)\\s*点\\s*(?:(\\d{1,2})\\s*分?)?"));
|
||
match = clockExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
const QString period = match.captured(1);
|
||
int hour = adjustedHour(period, parseSmallInteger(match.captured(2)));
|
||
const int minute = match.captured(3).isEmpty() ? 0 : match.captured(3).toInt();
|
||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("没有识别到有效提醒时间。")};
|
||
}
|
||
|
||
QDateTime remindAt(dateResolution.date, QTime(hour, minute));
|
||
if (remindAt <= now)
|
||
{
|
||
if (dateResolution.explicitDate)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("提醒时间必须晚于当前时间。")};
|
||
}
|
||
remindAt = remindAt.addDays(1);
|
||
}
|
||
|
||
return {
|
||
ReminderCommandType::Create,
|
||
extractTitle(text, match.captured(0)),
|
||
text,
|
||
remindAt,
|
||
{},
|
||
{},
|
||
};
|
||
}
|
||
|
||
const QRegularExpression colonClockExpression(QStringLiteral("(明天)?\\s*(?:在)?\\s*([01]?\\d|2[0-3])\\s*[::]\\s*([0-5]\\d)"));
|
||
match = colonClockExpression.match(text);
|
||
if (match.hasMatch())
|
||
{
|
||
QDateTime remindAt(dateResolution.date, QTime(match.captured(2).toInt(), match.captured(3).toInt()));
|
||
if (remindAt <= now)
|
||
{
|
||
if (dateResolution.explicitDate)
|
||
{
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("提醒时间必须晚于当前时间。")};
|
||
}
|
||
remindAt = remindAt.addDays(1);
|
||
}
|
||
|
||
return {
|
||
ReminderCommandType::Create,
|
||
extractTitle(text, match.captured(0)),
|
||
text,
|
||
remindAt,
|
||
{},
|
||
{},
|
||
};
|
||
}
|
||
|
||
return {ReminderCommandType::Invalid, {}, text, {}, {}, QStringLiteral("没有识别到提醒时间。支持如“10分钟后提醒我喝水”“明天9点提醒我开会”。")};
|
||
}
|
||
|
||
ReminderCommand ReminderParser::parseRecurringCreateCommand(const QString &text, const QDateTime &now) const
|
||
{
|
||
QRegularExpressionMatch match;
|
||
|
||
if (text.contains(QStringLiteral("每月"))
|
||
&& (text.contains(QStringLiteral("最后")) || text.contains(QStringLiteral("月末"))))
|
||
{
|
||
return invalidRecurringCommand(text, QStringLiteral("暂不支持每月最后一天提醒,目前支持每月具体日期。"));
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("每天")) || text.contains(QStringLiteral("每日")))
|
||
{
|
||
const ParsedReminderTime parsedTime = parseTimeExpression(text);
|
||
if (parsedTime.hour < 0)
|
||
{
|
||
return invalidRecurringCommand(text, QStringLiteral("没有识别到重复提醒时间。支持如“每天9点提醒我打卡”。"));
|
||
}
|
||
|
||
const QTime time(parsedTime.hour, parsedTime.minute);
|
||
ReminderRecurrence recurrence;
|
||
recurrence.type = ReminderRecurrenceType::Daily;
|
||
recurrence.interval = 1;
|
||
recurrence.hour = parsedTime.hour;
|
||
recurrence.minute = parsedTime.minute;
|
||
return createReminderCommand(
|
||
extractTitle(text, parsedTime.expression),
|
||
text,
|
||
nextDailyOccurrence(now, time),
|
||
recurrence);
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("每周")) || text.contains(QStringLiteral("每星期")))
|
||
{
|
||
const QRegularExpression weeklyExpression(QStringLiteral("(?:每周|每星期)\\s*([一二三四五六日天1-7])"));
|
||
match = weeklyExpression.match(text);
|
||
const int weekday = match.hasMatch() ? weekdayFromText(match.captured(1)) : -1;
|
||
const ParsedReminderTime parsedTime = parseTimeExpression(text);
|
||
|
||
if (weekday < 1 || parsedTime.hour < 0)
|
||
{
|
||
return invalidRecurringCommand(text, QStringLiteral("没有识别到重复提醒时间。支持如“每周一上午10点提醒我周会”。"));
|
||
}
|
||
|
||
const QTime time(parsedTime.hour, parsedTime.minute);
|
||
ReminderRecurrence recurrence;
|
||
recurrence.type = ReminderRecurrenceType::Weekly;
|
||
recurrence.interval = 1;
|
||
recurrence.weekday = weekday;
|
||
recurrence.hour = parsedTime.hour;
|
||
recurrence.minute = parsedTime.minute;
|
||
return createReminderCommand(
|
||
extractTitle(text, parsedTime.expression),
|
||
text,
|
||
nextWeeklyOccurrence(now, weekday, time),
|
||
recurrence);
|
||
}
|
||
|
||
if (text.contains(QStringLiteral("每月")))
|
||
{
|
||
const QRegularExpression monthlyExpression(QStringLiteral("每月\\s*([0-9]{1,2}|[一二两三四五六七八九十]+)\\s*(?:日|号)?"));
|
||
match = monthlyExpression.match(text);
|
||
const int monthDay = match.hasMatch() ? parseSmallInteger(match.captured(1)) : -1;
|
||
const ParsedReminderTime parsedTime = parseTimeExpression(text);
|
||
|
||
if (monthDay < 1 || monthDay > 31 || parsedTime.hour < 0)
|
||
{
|
||
return invalidRecurringCommand(text, QStringLiteral("没有识别到重复提醒时间。支持如“每月3号9点提醒我交报告”。"));
|
||
}
|
||
|
||
const QTime time(parsedTime.hour, parsedTime.minute);
|
||
const QDateTime remindAt = nextMonthlyOccurrence(now, monthDay, time);
|
||
if (!remindAt.isValid())
|
||
{
|
||
return invalidRecurringCommand(text, QStringLiteral("没有找到有效的每月提醒日期。"));
|
||
}
|
||
|
||
ReminderRecurrence recurrence;
|
||
recurrence.type = ReminderRecurrenceType::Monthly;
|
||
recurrence.interval = 1;
|
||
recurrence.monthDay = monthDay;
|
||
recurrence.hour = parsedTime.hour;
|
||
recurrence.minute = parsedTime.minute;
|
||
return createReminderCommand(
|
||
extractTitle(text, parsedTime.expression),
|
||
text,
|
||
remindAt,
|
||
recurrence);
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
QString ReminderParser::extractTitle(QString text, const QString &timeExpression) const
|
||
{
|
||
text = removeFirst(text, timeExpression);
|
||
text.remove(QRegularExpression(QStringLiteral("\\d{1,2}\\s*月\\s*\\d{1,2}\\s*(?:日|号)?")));
|
||
text.remove(QRegularExpression(QStringLiteral("\\d{1,2}\\s*/\\s*\\d{1,2}")));
|
||
text.remove(QRegularExpression(QStringLiteral("下周\\s*[一二三四五六日天1-7]")));
|
||
text.remove(QRegularExpression(QStringLiteral("每周\\s*[一二三四五六日天1-7]")));
|
||
text.remove(QRegularExpression(QStringLiteral("每星期\\s*[一二三四五六日天1-7]")));
|
||
text.remove(QRegularExpression(QStringLiteral("每月\\s*([0-9]{1,2}|[一二两三四五六七八九十]+)\\s*(?:日|号)?")));
|
||
|
||
const QStringList tokensToRemove = {
|
||
QStringLiteral("提醒我"),
|
||
QStringLiteral("提醒"),
|
||
QStringLiteral("叫我"),
|
||
QStringLiteral("到点"),
|
||
QStringLiteral("的时候"),
|
||
QStringLiteral("请"),
|
||
QStringLiteral("帮我"),
|
||
QStringLiteral("今天"),
|
||
QStringLiteral("明天"),
|
||
QStringLiteral("后天"),
|
||
QStringLiteral("每天"),
|
||
QStringLiteral("每日"),
|
||
QStringLiteral("每周"),
|
||
QStringLiteral("每星期"),
|
||
QStringLiteral("每月"),
|
||
};
|
||
|
||
for (const QString &token : tokensToRemove)
|
||
{
|
||
text.remove(token);
|
||
}
|
||
|
||
text = text.trimmed();
|
||
while (text.startsWith(QChar(0x3000)) || text.startsWith(QLatin1Char(' ')) || text.startsWith(QLatin1Char(',')) || text.startsWith(QChar(0xff0c)))
|
||
{
|
||
text.remove(0, 1);
|
||
text = text.trimmed();
|
||
}
|
||
|
||
return text.isEmpty() ? QStringLiteral("提醒") : text;
|
||
}
|