From 5e1ca81cef11aabee220758696b962440cc0c6c7 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 29 Jan 2021 08:16:18 -0600 Subject: [PATCH] Lparrish/dialog (#922) * dialogs.choices * bot-dialogs: periodic push (mostly functional ObjectPath) * Dialog Updates * Interim commit and push to protect work in progress * Pushing to protect work in progress * Safety Checkin * Setting up unit tests for Dialogs * Updates to get VS Code test runner working * Completed DialogManager and DialogStateManager * Updates to match namespaces and file structure. * First unit test for DialogStateManager now working. * Addin DialogStateManager unit tests and fixes. * Waterfall dialogs and Prompts * Fixes and unit tests * Choice Prompt unit tests * Additional unit tests and fixes * Unit tests and fixes. * ComponentDialog tests and fixes * DialogContainerTests and fixes to Dialog * Additional tests and fixes * Final unit tests * Correct failing unit test. * Fixes for merge issues. * Update to new exception handling pattern. * Remove uneeded unit test inports. * Added copyright notices. * Update DialogContext * Added recognizer libraries Co-authored-by: tracyboehrer --- etc/bot-checkstyle.xml | 4 +- .../com/microsoft/bot/builder/BotAdapter.java | 5 + .../com/microsoft/bot/builder/BotState.java | 62 +- .../bot/builder/BotTelemetryClient.java | 12 + .../bot/builder/ComponentRegistration.java | 34 + .../bot/builder/DelegatingTurnContext.java | 18 + .../microsoft/bot/builder/NextDelegate.java | 3 + .../bot/builder/NullBotTelemetryClient.java | 5 + .../bot/builder/OnTurnErrorHandler.java | 3 + .../bot/builder/RegisterClassMiddleware.java | 74 + .../microsoft/bot/builder/TurnContext.java | 22 +- .../bot/builder/TurnContextImpl.java | 30 + .../builder/TurnContextStateCollection.java | 57 +- .../bot/builder/ActivityHandlerTests.java | 3 + .../bot/builder/BotFrameworkAdapterTests.java | 3 + .../bot/builder/BotStateSetTests.java | 3 + .../bot/builder/InspectionTests.java | 4 +- .../bot/builder/MemoryConnectorClient.java | 3 + .../bot/builder/MemoryConversations.java | 3 + .../bot/builder/MemoryTranscriptTests.java | 3 + .../microsoft/bot/builder/MentionTests.java | 3 + .../bot/builder/MessageFactoryTests.java | 3 + .../bot/builder/MockAppCredentials.java | 3 + .../bot/builder/MockConnectorClient.java | 3 + .../bot/builder/OnTurnErrorTests.java | 3 + .../builder/ShowTypingMiddlewareTests.java | 3 + .../bot/builder/TelemetryMiddlewareTests.java | 3 + .../bot/builder/TestAdapterTests.java | 3 + .../bot/builder/TranscriptBaseTests.java | 3 + .../bot/builder/adapters/TestAdapter.java | 74 + .../bot/builder/adapters/TestFlow.java | 14 +- .../TeamsActivityHandlerHidingTests.java | 3 + .../com/microsoft/bot/connector/Channels.java | 5 + .../bot/connector/Conversations.java | 6 +- .../microsoft/bot/connector/OAuthClient.java | 3 + .../bot/connector/OAuthClientConfig.java | 3 + .../AuthenticationConstants.java | 5 + .../AuthenticationException.java | 3 + .../CredentialsAuthenticator.java | 3 + .../authentication/SkillValidation.java | 103 + .../bot/connector/rest/RestOAuthClient.java | 3 + .../connector/teams/TeamsConnectorClient.java | 3 + .../restclient/interceptors/package-info.java | 3 + .../bot/restclient/package-info.java | 3 + .../bot/restclient/protocol/package-info.java | 3 + .../bot/restclient/retry/package-info.java | 3 + .../restclient/serializer/package-info.java | 3 + .../bot/connector/AttachmentsTest.java | 3 + .../bot/connector/BotAccessTokenStub.java | 3 + .../bot/connector/BotConnectorTestBase.java | 3 + .../bot/connector/JwtTokenExtractorTests.java | 3 + .../bot/connector/OAuthConnectorTests.java | 3 + .../bot/connector/OAuthTestBase.java | 3 + .../bot/connector/RetryParamsTests.java | 3 + .../microsoft/bot/connector/RetryTests.java | 3 + .../bot/connector/UserAgentTest.java | 3 + .../connector/base/InterceptorManager.java | 3 + .../bot/connector/base/NetworkCallRecord.java | 3 + .../bot/connector/base/RecordedData.java | 3 + .../bot/connector/base/TestBase.java | 3 + .../bot/restclient/AnimalShelter.java | 3 + .../AnimalWithTypeIdContainingDot.java | 3 + .../CatWithTypeIdContainingDot.java | 3 + .../bot/restclient/ComposeTurtles.java | 3 + .../DogWithTypeIdContainingDot.java | 3 + .../bot/restclient/FlattenableAnimalInfo.java | 3 + ...NonEmptyAnimalWithTypeIdContainingDot.java | 3 + .../RabbitWithTypeIdContainingDot.java | 3 + .../TurtleWithTypeIdContainingDot.java | 3 + libraries/bot-dialogs/pom.xml | 51 +- .../bot/dialogs/ComponentDialog.java | 418 ++++ .../com/microsoft/bot/dialogs/Dialog.java | 336 ++- .../bot/dialogs/DialogCompletion.java | 54 - .../bot/dialogs/DialogContainer.java | 174 +- .../microsoft/bot/dialogs/DialogContext.java | 674 ++++-- .../bot/dialogs/DialogContextPath.java | 29 + .../bot/dialogs/DialogDependencies.java | 18 + .../microsoft/bot/dialogs/DialogEvent.java | 63 + .../microsoft/bot/dialogs/DialogEvents.java | 29 + .../microsoft/bot/dialogs/DialogInstance.java | 109 + .../microsoft/bot/dialogs/DialogManager.java | 408 ++++ .../bot/dialogs/DialogManagerResult.java | 68 + .../com/microsoft/bot/dialogs/DialogPath.java | 64 + .../microsoft/bot/dialogs/DialogReason.java | 27 + .../com/microsoft/bot/dialogs/DialogSet.java | 214 ++ .../microsoft/bot/dialogs/DialogState.java | 48 + .../bot/dialogs/DialogTurnResult.java | 90 + .../bot/dialogs/DialogTurnStatus.java | 26 + .../dialogs/DialogsComponentRegistration.java | 67 + .../com/microsoft/bot/dialogs/IDialog.java | 19 - .../bot/dialogs/IDialogContinue.java | 21 - .../microsoft/bot/dialogs/MessageOptions.java | 57 - .../com/microsoft/bot/dialogs/ObjectPath.java | 865 ++++++++ .../microsoft/bot/dialogs/PersistedState.java | 74 + .../bot/dialogs/PersistedStateKeys.java | 48 + .../com/microsoft/bot/dialogs/ScopePath.java | 58 + .../com/microsoft/bot/dialogs/ThisPath.java | 19 + .../com/microsoft/bot/dialogs/TurnPath.java | 57 + .../bot/dialogs/WaterfallDialog.java | 277 +++ .../microsoft/bot/dialogs/WaterfallStep.java | 23 + .../bot/dialogs/WaterfallStepContext.java | 132 ++ .../bot/dialogs/choices/Channel.java | 145 ++ .../microsoft/bot/dialogs/choices/Choice.java | 85 + .../bot/dialogs/choices/ChoiceFactory.java | 399 ++++ .../dialogs/choices/ChoiceFactoryOptions.java | 173 ++ .../dialogs/choices/ChoiceRecognizers.java | 141 ++ .../microsoft/bot/dialogs/choices/Find.java | 311 +++ .../dialogs/choices/FindChoicesOptions.java | 96 + .../dialogs/choices/FindValuesOptions.java | 100 + .../bot/dialogs/choices/FoundChoice.java | 91 + .../bot/dialogs/choices/FoundValue.java | 73 + .../bot/dialogs/choices/ListStyle.java | 28 + .../bot/dialogs/choices/ModelResult.java | 99 + .../bot/dialogs/choices/SortedValue.java | 59 + .../microsoft/bot/dialogs/choices/Token.java | 92 + .../bot/dialogs/choices/Tokenizer.java | 86 + .../dialogs/choices/TokenizerFunction.java | 21 + .../bot/dialogs/choices/package-info.java | 8 + .../dialogs/memory/ComponentMemoryScopes.java | 19 + .../memory/ComponentPathResolvers.java | 15 + .../dialogs/memory/DialogStateManager.java | 834 ++++++++ .../DialogStateManagerConfiguration.java | 53 + .../bot/dialogs/memory/PathResolver.java | 16 + .../PathResolvers/AliasPathResolver.java | 90 + .../PathResolvers/AtAtPathResolver.java | 17 + .../memory/PathResolvers/AtPathResolver.java | 53 + .../PathResolvers/DollarPathResolver.java | 17 + .../PathResolvers/HashPathResolver.java | 17 + .../PathResolvers/PercentPathResolver.java | 17 + .../memory/PathResolvers/package-info.java | 8 + .../bot/dialogs/memory/package-info.java | 8 + .../memory/scopes/BotStateMemoryScope.java | 91 + .../memory/scopes/ClassMemoryScope.java | 47 + .../scopes/ConversationMemoryScope.java | 19 + .../memory/scopes/DialogClassMemoryScope.java | 56 + .../scopes/DialogContextMemoryScope.java | 84 + .../memory/scopes/DialogMemoryScope.java | 82 + .../dialogs/memory/scopes/MemoryScope.java | 114 + .../dialogs/memory/scopes/ReadOnlyObject.java | 129 ++ .../memory/scopes/SettingsMemoryScope.java | 73 + .../memory/scopes/ThisMemoryScope.java | 44 + .../memory/scopes/TurnMemoryScope.java | 57 + .../memory/scopes/UserMemoryScope.java | 19 + .../dialogs/memory/scopes/package-info.java | 8 + .../microsoft/bot/dialogs/package-info.java | 8 + .../bot/dialogs/prompts/ActivityPrompt.java | 290 +++ .../bot/dialogs/prompts/AttachmentPrompt.java | 118 + .../microsoft/bot/dialogs/prompts/Choice.java | 43 - .../bot/dialogs/prompts/ChoicePrompt.java | 311 +++ .../bot/dialogs/prompts/ConfirmPrompt.java | 353 +++ .../bot/dialogs/prompts/DateTimePrompt.java | 186 ++ .../dialogs/prompts/DateTimeResolution.java | 99 + .../bot/dialogs/prompts/NumberPrompt.java | 252 +++ .../microsoft/bot/dialogs/prompts/Prompt.java | 382 ++++ .../dialogs/prompts/PromptCultureModel.java | 133 ++ .../dialogs/prompts/PromptCultureModels.java | 308 +++ .../bot/dialogs/prompts/PromptOptions.java | 107 + .../prompts/PromptRecognizerResult.java | 81 + .../bot/dialogs/prompts/PromptValidator.java | 28 + .../prompts/PromptValidatorContext.java | 93 + .../bot/dialogs/prompts/TextPrompt.java | 135 ++ .../bot/dialogs/prompts/package-info.java | 8 + .../microsoft/recognizers/text/Culture.java | 41 + .../recognizers/text/CultureInfo.java | 14 + .../recognizers/text/ExtendedModelResult.java | 19 + .../recognizers/text/ExtractResult.java | 103 + .../recognizers/text/IExtractor.java | 7 + .../microsoft/recognizers/text/IModel.java | 10 + .../microsoft/recognizers/text/IParser.java | 5 + .../microsoft/recognizers/text/Metadata.java | 49 + .../recognizers/text/ModelFactory.java | 81 + .../recognizers/text/ModelResult.java | 20 + .../recognizers/text/ParseResult.java | 46 + .../recognizers/text/Recognizer.java | 42 + .../recognizers/text/ResolutionKey.java | 10 + .../text/choice/ChoiceOptions.java | 5 + .../text/choice/ChoiceRecognizer.java | 74 + .../recognizers/text/choice/Constants.java | 8 + .../config/BooleanParserConfiguration.java | 19 + .../config/IChoiceParserConfiguration.java | 7 + .../EnglishBooleanExtractorConfiguration.java | 70 + .../choice/extractors/BooleanExtractor.java | 9 + .../extractors/ChoiceExtractDataResult.java | 19 + .../choice/extractors/ChoiceExtractor.java | 170 ++ .../IBooleanExtractorConfiguration.java | 9 + .../IChoiceExtractorConfiguration.java | 16 + .../text/choice/models/BooleanModel.java | 43 + .../text/choice/models/ChoiceModel.java | 43 + .../text/choice/parsers/BooleanParser.java | 9 + .../text/choice/parsers/ChoiceParser.java | 43 + .../parsers/OptionsOtherMatchParseResult.java | 14 + .../parsers/OptionsParseDataResult.java | 15 + .../text/choice/resources/ChineseChoice.java | 23 + .../text/choice/resources/EnglishChoice.java | 23 + .../text/choice/resources/FrenchChoice.java | 23 + .../choice/resources/PortugueseChoice.java | 23 + .../text/choice/resources/SpanishChoice.java | 23 + .../text/choice/utilities/UnicodeUtils.java | 29 + .../recognizers/text/datetime/Constants.java | 182 ++ .../text/datetime/DatePeriodTimexType.java | 8 + .../text/datetime/DateTimeOptions.java | 26 + .../text/datetime/DateTimeRecognizer.java | 93 + .../text/datetime/DateTimeResolutionKey.java | 11 + .../text/datetime/TimeTypeConstants.java | 18 + .../config/BaseOptionsConfiguration.java | 31 + .../config/IOptionsConfiguration.java | 10 + .../EnglishDateExtractorConfiguration.java | 241 +++ ...glishDatePeriodExtractorConfiguration.java | 311 +++ ...lishDateTimeAltExtractorConfiguration.java | 90 + ...EnglishDateTimeExtractorConfiguration.java | 160 ++ ...hDateTimePeriodExtractorConfiguration.java | 281 +++ ...EnglishDurationExtractorConfiguration.java | 138 ++ .../EnglishHolidayExtractorConfiguration.java | 31 + .../EnglishMergedExtractorConfiguration.java | 220 ++ .../EnglishSetExtractorConfiguration.java | 120 ++ .../EnglishTimeExtractorConfiguration.java | 140 ++ ...glishTimePeriodExtractorConfiguration.java | 132 ++ ...EnglishTimeZoneExtractorConfiguration.java | 93 + ...lishCommonDateTimeParserConfiguration.java | 290 +++ .../EnglishDateParserConfiguration.java | 351 +++ .../EnglishDatePeriodParserConfiguration.java | 575 +++++ ...EnglishDateTimeAltParserConfiguration.java | 48 + .../EnglishDateTimeParserConfiguration.java | 256 +++ ...lishDateTimePeriodParserConfiguration.java | 337 +++ .../EnglishDurationParserConfiguration.java | 145 ++ .../EnglishHolidayParserConfiguration.java | 225 ++ .../EnglishMergedParserConfiguration.java | 77 + .../EnglishSetParserConfiguration.java | 251 +++ .../EnglishTimeParserConfiguration.java | 187 ++ .../EnglishTimePeriodParserConfiguration.java | 159 ++ .../datetime/english/parsers/TimeParser.java | 63 + .../EnglishDatetimeUtilityConfiguration.java | 77 + .../extractors/AbstractYearExtractor.java | 108 + .../extractors/BaseDateExtractor.java | 478 +++++ .../extractors/BaseDatePeriodExtractor.java | 464 ++++ .../extractors/BaseDateTimeAltExtractor.java | 598 ++++++ .../extractors/BaseDateTimeExtractor.java | 292 +++ .../BaseDateTimePeriodExtractor.java | 575 +++++ .../extractors/BaseDurationExtractor.java | 281 +++ .../extractors/BaseHolidayExtractor.java | 60 + .../BaseMergedDateTimeExtractor.java | 361 ++++ .../datetime/extractors/BaseSetExtractor.java | 160 ++ .../extractors/BaseTimeExtractor.java | 149 ++ .../extractors/BaseTimePeriodExtractor.java | 275 +++ .../extractors/BaseTimeZoneExtractor.java | 133 ++ .../datetime/extractors/IDateExtractor.java | 7 + .../extractors/IDateTimeExtractor.java | 13 + .../extractors/IDateTimeListExtractor.java | 12 + .../extractors/IDateTimeZoneExtractor.java | 9 + .../config/IDateExtractorConfiguration.java | 62 + .../IDatePeriodExtractorConfiguration.java | 79 + .../IDateTimeAltExtractorConfiguration.java | 24 + .../IDateTimeExtractorConfiguration.java | 48 + ...IDateTimePeriodExtractorConfiguration.java | 81 + .../IDurationExtractorConfiguration.java | 45 + .../IHolidayExtractorConfiguration.java | 7 + .../config/IMergedExtractorConfiguration.java | 68 + .../config/ISetExtractorConfiguration.java | 38 + .../config/ITimeExtractorConfiguration.java | 18 + .../ITimePeriodExtractorConfiguration.java | 31 + .../ITimeZoneExtractorConfiguration.java | 19 + .../config/ProcessedSuperfluousWords.java | 23 + .../extractors/config/ResultIndex.java | 27 + .../extractors/config/ResultTimex.java | 27 + .../FrenchDateExtractorConfiguration.java | 245 +++ ...renchDatePeriodExtractorConfiguration.java | 328 +++ ...enchDateTimeAltExtractorConfiguration.java | 86 + .../FrenchDateTimeExtractorConfiguration.java | 171 ++ ...hDateTimePeriodExtractorConfiguration.java | 283 +++ .../FrenchDurationExtractorConfiguration.java | 139 ++ .../FrenchHolidayExtractorConfiguration.java | 38 + .../FrenchMergedExtractorConfiguration.java | 194 ++ .../FrenchSetExtractorConfiguration.java | 113 + .../FrenchTimeExtractorConfiguration.java | 87 + ...renchTimePeriodExtractorConfiguration.java | 150 ++ .../FrenchTimeZoneExtractorConfiguration.java | 43 + ...enchCommonDateTimeParserConfiguration.java | 292 +++ .../FrenchDateParserConfiguration.java | 336 +++ .../FrenchDatePeriodParserConfiguration.java | 559 +++++ .../FrenchDateTimeAltParserConfiguration.java | 48 + .../FrenchDateTimeParserConfiguration.java | 258 +++ ...enchDateTimePeriodParserConfiguration.java | 331 +++ .../FrenchDurationParserConfiguration.java | 144 ++ .../FrenchHolidayParserConfiguration.java | 226 ++ .../FrenchMergedParserConfiguration.java | 75 + .../parsers/FrenchSetParserConfiguration.java | 195 ++ .../french/parsers/FrenchTimeParser.java | 57 + .../FrenchTimeParserConfiguration.java | 156 ++ .../FrenchTimePeriodParserConfiguration.java | 158 ++ .../FrenchDatetimeUtilityConfiguration.java | 87 + .../text/datetime/models/DateTimeModel.java | 94 + .../text/datetime/parsers/BaseDateParser.java | 739 +++++++ .../parsers/BaseDatePeriodParser.java | 1897 +++++++++++++++++ .../parsers/BaseDateTimeAltParser.java | 256 +++ .../datetime/parsers/BaseDateTimeParser.java | 398 ++++ .../parsers/BaseDateTimePeriodParser.java | 1036 +++++++++ .../datetime/parsers/BaseDurationParser.java | 414 ++++ .../datetime/parsers/BaseHolidayParser.java | 204 ++ .../BaseHolidayParserConfiguration.java | 163 ++ .../parsers/BaseMergedDateTimeParser.java | 834 ++++++++ .../text/datetime/parsers/BaseSetParser.java | 251 +++ .../text/datetime/parsers/BaseTimeParser.java | 358 ++++ .../parsers/BaseTimePeriodParser.java | 697 ++++++ .../datetime/parsers/BaseTimeZoneParser.java | 168 ++ .../datetime/parsers/DateTimeParseResult.java | 37 + .../datetime/parsers/IDateTimeParser.java | 15 + .../config/BaseDateParserConfiguration.java | 17 + .../ICommonDateTimeParserConfiguration.java | 80 + .../config/IDateParserConfiguration.java | 100 + .../IDatePeriodParserConfiguration.java | 155 ++ .../IDateTimeAltParserConfiguration.java | 17 + .../config/IDateTimeParserConfiguration.java | 70 + .../IDateTimePeriodParserConfiguration.java | 84 + .../config/IDurationParserConfiguration.java | 44 + .../config/IHolidayParserConfiguration.java | 24 + .../config/IMergedParserConfiguration.java | 29 + .../config/ISetParserConfiguration.java | 58 + .../config/ITimeParserConfiguration.java | 26 + .../ITimePeriodParserConfiguration.java | 40 + .../config/MatchedTimeRangeResult.java | 57 + .../parsers/config/PrefixAdjustResult.java | 13 + .../parsers/config/SuffixAdjustResult.java | 17 + .../text/datetime/resources/BaseDateTime.java | 111 + .../datetime/resources/ChineseDateTime.java | 1016 +++++++++ .../datetime/resources/EnglishDateTime.java | 1446 +++++++++++++ .../datetime/resources/EnglishTimeZone.java | 374 ++++ .../datetime/resources/FrenchDateTime.java | 1234 +++++++++++ .../resources/PortugueseDateTime.java | 983 +++++++++ .../datetime/resources/SpanishDateTime.java | 1204 +++++++++++ .../SpanishDateExtractorConfiguration.java | 245 +++ ...anishDatePeriodExtractorConfiguration.java | 320 +++ ...nishDateTimeAltExtractorConfiguration.java | 90 + ...SpanishDateTimeExtractorConfiguration.java | 170 ++ ...hDateTimePeriodExtractorConfiguration.java | 283 +++ ...SpanishDurationExtractorConfiguration.java | 139 ++ .../SpanishHolidayExtractorConfiguration.java | 36 + .../SpanishMergedExtractorConfiguration.java | 198 ++ .../SpanishSetExtractorConfiguration.java | 119 ++ .../SpanishTimeExtractorConfiguration.java | 132 ++ ...anishTimePeriodExtractorConfiguration.java | 154 ++ ...SpanishTimeZoneExtractorConfiguration.java | 44 + .../spanish/parsers/DateTimePeriodParser.java | 133 ++ ...nishCommonDateTimeParserConfiguration.java | 287 +++ .../SpanishDateParserConfiguration.java | 336 +++ .../SpanishDatePeriodParserConfiguration.java | 621 ++++++ ...SpanishDateTimeAltParserConfiguration.java | 48 + .../SpanishDateTimeParserConfiguration.java | 237 ++ ...nishDateTimePeriodParserConfiguration.java | 347 +++ .../SpanishDurationParserConfiguration.java | 145 ++ .../SpanishHolidayParserConfiguration.java | 137 ++ .../SpanishMergedParserConfiguration.java | 76 + .../SpanishSetParserConfiguration.java | 218 ++ .../SpanishTimeParserConfiguration.java | 161 ++ .../SpanishTimePeriodParserConfiguration.java | 152 ++ .../SpanishDatetimeUtilityConfiguration.java | 86 + .../text/datetime/utilities/AgoLaterUtil.java | 197 ++ .../datetime/utilities/ConditionalMatch.java | 27 + .../text/datetime/utilities/DateContext.java | 165 ++ .../utilities/DateTimeFormatUtil.java | 248 +++ .../utilities/DateTimeResolutionResult.java | 134 ++ .../text/datetime/utilities/DateUtil.java | 156 ++ .../utilities/DurationParsingUtil.java | 187 ++ .../utilities/GetModAndDateResult.java | 22 + .../datetime/utilities/HolidayFunctions.java | 31 + .../IDateTimeUtilityConfiguration.java | 28 + .../utilities/MatchedTimexResult.java | 31 + .../text/datetime/utilities/MatchingUtil.java | 106 + .../utilities/MatchingUtilResult.java | 15 + .../utilities/NthBusinessDayResult.java | 14 + .../utilities/RangeTimexComponents.java | 11 + .../datetime/utilities/RegexExtension.java | 58 + .../datetime/utilities/StringExtension.java | 13 + .../utilities/TimeOfDayResolutionResult.java | 50 + .../utilities/TimeZoneResolutionResult.java | 26 + .../datetime/utilities/TimeZoneUtility.java | 64 + .../text/datetime/utilities/TimexUtility.java | 314 +++ .../text/datetime/utilities/Token.java | 87 + .../recognizers/text/matcher/AaNode.java | 53 + .../text/matcher/AbstractMatcher.java | 21 + .../text/matcher/AcAutomation.java | 89 + .../recognizers/text/matcher/IMatcher.java | 10 + .../recognizers/text/matcher/ITokenizer.java | 7 + .../recognizers/text/matcher/MatchResult.java | 66 + .../text/matcher/MatchStrategy.java | 6 + .../recognizers/text/matcher/Node.java | 43 + .../text/matcher/NumberWithUnitTokenizer.java | 86 + .../text/matcher/SimpleTokenizer.java | 78 + .../text/matcher/StringMatcher.java | 99 + .../recognizers/text/matcher/Token.java | 30 + .../recognizers/text/matcher/TrieTree.java | 67 + .../recognizers/text/number/Constants.java | 21 + .../text/number/LongFormatType.java | 53 + .../recognizers/text/number/NumberMode.java | 12 + .../text/number/NumberOptions.java | 16 + .../text/number/NumberRangeConstants.java | 21 + .../text/number/NumberRecognizer.java | 217 ++ .../chinese/ChineseNumberExtractorMode.java | 18 + .../chinese/extractors/CardinalExtractor.java | 41 + .../chinese/extractors/DoubleExtractor.java | 48 + .../chinese/extractors/FractionExtractor.java | 39 + .../chinese/extractors/IntegerExtractor.java | 66 + .../chinese/extractors/NumberExtractor.java | 63 + .../extractors/NumberRangeExtractor.java | 78 + .../chinese/extractors/OrdinalExtractor.java | 38 + .../extractors/PercentageExtractor.java | 99 + .../ChineseNumberParserConfiguration.java | 82 + ...ChineseNumberRangeParserConfiguration.java | 96 + .../english/extractors/CardinalExtractor.java | 66 + .../english/extractors/DoubleExtractor.java | 78 + .../english/extractors/FractionExtractor.java | 74 + .../english/extractors/IntegerExtractor.java | 75 + .../english/extractors/NumberExtractor.java | 116 + .../extractors/NumberRangeExtractor.java | 56 + .../english/extractors/OrdinalExtractor.java | 65 + .../extractors/PercentageExtractor.java | 47 + .../EnglishNumberParserConfiguration.java | 105 + ...EnglishNumberRangeParserConfiguration.java | 97 + .../extractors/BaseNumberExtractor.java | 155 ++ .../extractors/BaseNumberRangeExtractor.java | 232 ++ .../extractors/BasePercentageExtractor.java | 241 +++ .../number/extractors/PreProcessResult.java | 18 + .../french/extractors/CardinalExtractor.java | 55 + .../french/extractors/DoubleExtractor.java | 48 + .../french/extractors/FractionExtractor.java | 41 + .../french/extractors/IntegerExtractor.java | 47 + .../french/extractors/NumberExtractor.java | 106 + .../french/extractors/OrdinalExtractor.java | 35 + .../extractors/PercentageExtractor.java | 41 + .../FrenchNumberParserConfiguration.java | 105 + .../german/extractors/CardinalExtractor.java | 55 + .../german/extractors/DoubleExtractor.java | 48 + .../german/extractors/FractionExtractor.java | 42 + .../german/extractors/IntegerExtractor.java | 49 + .../german/extractors/NumberExtractor.java | 110 + .../german/extractors/OrdinalExtractor.java | 37 + .../extractors/PercentageExtractor.java | 39 + .../GermanNumberParserConfiguration.java | 113 + .../number/models/AbstractNumberModel.java | 63 + .../text/number/models/NumberModel.java | 17 + .../text/number/models/NumberRangeModel.java | 17 + .../text/number/models/OrdinalModel.java | 17 + .../text/number/models/PercentModel.java | 17 + .../parsers/AgnosticNumberParserFactory.java | 52 + .../parsers/AgnosticNumberParserType.java | 11 + .../number/parsers/BaseCJKNumberParser.java | 498 +++++ .../BaseCJKNumberParserConfiguration.java | 314 +++ .../text/number/parsers/BaseNumberParser.java | 635 ++++++ .../BaseNumberParserConfiguration.java | 171 ++ .../number/parsers/BaseNumberRangeParser.java | 224 ++ .../number/parsers/BasePercentageParser.java | 67 + .../ICJKNumberParserConfiguration.java | 57 + .../parsers/INumberParserConfiguration.java | 79 + .../INumberRangeParserConfiguration.java | 29 + .../number/parsers/NumberFormatUtility.java | 94 + .../extractors/CardinalExtractor.java | 56 + .../extractors/DoubleExtractor.java | 47 + .../extractors/FractionExtractor.java | 42 + .../extractors/IntegerExtractor.java | 48 + .../extractors/NumberExtractor.java | 104 + .../extractors/OrdinalExtractor.java | 35 + .../extractors/PercentageExtractor.java | 39 + .../PortugueseNumberParserConfiguration.java | 142 ++ .../text/number/resources/BaseNumbers.java | 48 + .../text/number/resources/ChineseNumeric.java | 522 +++++ .../text/number/resources/EnglishNumeric.java | 504 +++++ .../text/number/resources/FrenchNumeric.java | 508 +++++ .../text/number/resources/GermanNumeric.java | 512 +++++ .../number/resources/PortugueseNumeric.java | 536 +++++ .../text/number/resources/SpanishNumeric.java | 629 ++++++ .../spanish/extractors/CardinalExtractor.java | 56 + .../spanish/extractors/DoubleExtractor.java | 47 + .../spanish/extractors/FractionExtractor.java | 42 + .../spanish/extractors/IntegerExtractor.java | 64 + .../spanish/extractors/NumberExtractor.java | 104 + .../spanish/extractors/OrdinalExtractor.java | 52 + .../extractors/PercentageExtractor.java | 38 + .../SpanishNumberParserConfiguration.java | 129 ++ .../text/numberwithunit/Constants.java | 19 + .../numberwithunit/NumberWithUnitOptions.java | 15 + .../NumberWithUnitRecognizer.java | 256 +++ .../extractors/AgeExtractorConfiguration.java | 43 + ...eNumberWithUnitExtractorConfiguration.java | 112 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 58 + .../parsers/AgeParserConfiguration.java | 19 + ...neseNumberWithUnitParserConfiguration.java | 39 + .../parsers/CurrencyParserConfiguration.java | 32 + .../parsers/DimensionParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../extractors/AgeExtractorConfiguration.java | 45 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + ...hNumberWithUnitExtractorConfiguration.java | 77 + .../LengthExtractorConfiguration.java | 43 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 32 + .../parsers/DimensionParserConfiguration.java | 18 + ...lishNumberWithUnitParserConfiguration.java | 37 + .../parsers/LengthParserConfiguration.java | 18 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../extractors/BaseMergedUnitExtractor.java | 185 ++ ...INumberWithUnitExtractorConfiguration.java | 38 + .../extractors/NumberWithUnitExtractor.java | 427 ++++ .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + ...hNumberWithUnitExtractorConfiguration.java | 77 + .../LengthExtractorConfiguration.java | 43 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 19 + .../parsers/DimensionParserConfiguration.java | 18 + ...enchNumberWithUnitParserConfiguration.java | 38 + .../parsers/LengthParserConfiguration.java | 18 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 23 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + ...nNumberWithUnitExtractorConfiguration.java | 75 + .../LengthExtractorConfiguration.java | 43 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 18 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 18 + .../parsers/DimensionParserConfiguration.java | 18 + ...rmanNumberWithUnitParserConfiguration.java | 37 + .../parsers/LengthParserConfiguration.java | 18 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../models/AbstractNumberWithUnitModel.java | 103 + .../text/numberwithunit/models/AgeModel.java | 18 + .../numberwithunit/models/CurrencyModel.java | 18 + .../models/CurrencyUnitValue.java | 14 + .../numberwithunit/models/DimensionModel.java | 18 + .../models/PrefixUnitResult.java | 11 + .../models/TemperatureModel.java | 18 + .../text/numberwithunit/models/UnitValue.java | 12 + .../parsers/BaseCurrencyParser.java | 188 ++ .../parsers/BaseMergedUnitParser.java | 27 + ...BaseNumberWithUnitParserConfiguration.java | 72 + .../INumberWithUnitParserConfiguration.java | 30 + .../parsers/NumberWithUnitParser.java | 143 ++ .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + .../LengthExtractorConfiguration.java | 43 + ...eNumberWithUnitExtractorConfiguration.java | 76 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 19 + .../parsers/DimensionParserConfiguration.java | 18 + .../parsers/LengthParserConfiguration.java | 18 + ...ueseNumberWithUnitParserConfiguration.java | 39 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../resources/BaseCurrency.java | 281 +++ .../numberwithunit/resources/BaseUnits.java | 32 + .../resources/ChineseNumericWithUnit.java | 639 ++++++ .../resources/EnglishNumericWithUnit.java | 721 +++++++ .../resources/FrenchNumericWithUnit.java | 401 ++++ .../resources/GermanNumericWithUnit.java | 451 ++++ .../resources/JapaneseNumericWithUnit.java | 546 +++++ .../resources/PortugueseNumericWithUnit.java | 510 +++++ .../resources/SpanishNumericWithUnit.java | 517 +++++ .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + .../LengthExtractorConfiguration.java | 43 + ...hNumberWithUnitExtractorConfiguration.java | 76 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 19 + .../parsers/DimensionParserConfiguration.java | 18 + .../parsers/LengthParserConfiguration.java | 18 + ...nishNumberWithUnitParserConfiguration.java | 39 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../utilities/DictionaryUtils.java | 41 + .../utilities/StringComparer.java | 27 + .../text/resources/CodeGenerator.java | 132 ++ .../text/resources/ResourceConfig.java | 8 + .../text/resources/ResourceDefinitions.java | 8 + .../text/resources/ResourcesGenerator.java | 49 + .../text/resources/datatypes/Dictionary.java | 8 + .../text/resources/datatypes/List.java | 6 + .../text/resources/datatypes/NestedRegex.java | 6 + .../text/resources/datatypes/ParamsRegex.java | 6 + .../text/resources/datatypes/SimpleRegex.java | 5 + .../resources/writters/BooleanWriter.java | 20 + .../resources/writters/CharacterWriter.java | 20 + .../resources/writters/DefaultWriter.java | 21 + .../resources/writters/DictionaryWriter.java | 94 + .../text/resources/writters/ICodeWriter.java | 36 + .../resources/writters/IntegerWriter.java | 21 + .../text/resources/writters/ListWriter.java | 31 + .../resources/writters/NestedRegexWriter.java | 24 + .../resources/writters/ParamsRegexWriter.java | 29 + .../resources/writters/SimpleRegexWriter.java | 19 + .../recognizers/text/utilities/Capture.java | 13 + .../text/utilities/DefinitionLoader.java | 25 + .../text/utilities/DoubleUtility.java | 12 + .../text/utilities/FormatUtility.java | 83 + .../text/utilities/IntegerUtility.java | 12 + .../recognizers/text/utilities/Match.java | 25 + .../text/utilities/MatchGroup.java | 15 + .../text/utilities/QueryProcessor.java | 120 ++ .../text/utilities/RegExpUtility.java | 419 ++++ .../utilities/StringReplacerCallback.java | 7 + .../text/utilities/StringUtility.java | 27 + .../src/main/resources/naughtyStrings.txt | 742 +++++++ .../bot/dialogs/ComponentDialogTests.java | 557 +++++ .../bot/dialogs/DialogContainerTests.java | 113 + .../bot/dialogs/DialogManagerTests.java | 524 +++++ .../microsoft/bot/dialogs/DialogSetTests.java | 211 ++ .../bot/dialogs/DialogStateManagerTests.java | 266 +++ .../microsoft/bot/dialogs/LamdbaDialog.java | 46 + .../bot/dialogs/MemoryScopeTests.java | 125 ++ .../bot/dialogs/ObjectPathTests.java | 506 +++++ .../bot/dialogs/PromptCultureModelTests.java | 76 + .../dialogs/PromptValidatorContextTests.java | 203 ++ .../bot/dialogs/ReplaceDialogTests.java | 218 ++ .../com/microsoft/bot/dialogs/TestLocale.java | 171 ++ .../microsoft/bot/dialogs/WaterfallTests.java | 545 +++++ .../dialogs/choices/ChoiceFactoryTests.java | 191 ++ .../dialogs/choices/ChoicesChannelTests.java | 133 ++ .../choices/ChoicesRecognizerTests.java | 218 ++ .../choices/ChoicesTokenizerTests.java | 82 + .../dialogs/prompts/ActivityPromptTests.java | 314 +++ .../prompts/AttachmentPromptTests.java | 153 ++ .../ChoicePromptLocaleVariationTests.java | 142 ++ .../dialogs/prompts/ChoicePromptTests.java | 833 ++++++++ .../prompts/ConfirmPromptLocTests.java | 200 ++ .../dialogs/prompts/ConfirmPromptTests.java | 390 ++++ .../dialogs/prompts/DateTimePromptTests.java | 192 ++ .../dialogs/prompts/EventActivityPrompt.java | 28 + .../bot/dialogs/prompts/NumberPromptMock.java | 36 + .../dialogs/prompts/NumberPromptTests.java | 635 ++++++ .../bot/dialogs/prompts/TextPromptTests.java | 325 +++ .../ClasspathPropertiesConfiguration.java | 13 +- .../bot/integration/Configuration.java | 11 +- .../microsoft/bot/schema/Serialization.java | 119 ++ .../microsoft/bot/schema/SignInConstants.java | 3 + .../schema/teams/MeetingParticipantInfo.java | 3 + .../microsoft/bot/schema/ActivityTest.java | 3 + .../microsoft/bot/schema/CardActionTest.java | 3 + pom.xml | 2 +- .../models/AdaptiveCard.java | 3 + .../teamsactionpreview/models/Body.java | 3 + .../teamsactionpreview/models/Choice.java | 3 + .../teamstaskmodule/TeamsTaskModuleBot.java | 2 +- 688 files changed, 79326 insertions(+), 498 deletions(-) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/ChineseNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java create mode 100644 libraries/bot-dialogs/src/main/resources/naughtyStrings.txt create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java diff --git a/etc/bot-checkstyle.xml b/etc/bot-checkstyle.xml index 41b3575cb..337417b9e 100644 --- a/etc/bot-checkstyle.xml +++ b/etc/bot-checkstyle.xml @@ -159,7 +159,9 @@ - + + + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java index 2d2006e6b..c9d2f64f7 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java @@ -13,6 +13,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; /** * Represents a bot adapter that can connect a bot to a service endpoint. This @@ -194,6 +195,10 @@ protected CompletableFuture runPipeline( // Call any registered Middleware Components looking for ReceiveActivity() if (context.getActivity() != null) { + if (!StringUtils.isEmpty(context.getActivity().getLocale())) { + context.setLocale(context.getActivity().getLocale()); + } + return middlewareSet.receiveActivityWithStatus(context, callback) .exceptionally(exception -> { if (onTurnError != null) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java index e0261da7c..5c56dfbff 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java @@ -175,7 +175,9 @@ public CompletableFuture saveChanges(TurnContext turnContext, boolean forc */ public CompletableFuture clearState(TurnContext turnContext) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } turnContext.getTurnState().replace(contextServiceKey, new CachedBotState()); @@ -190,7 +192,9 @@ public CompletableFuture clearState(TurnContext turnContext) { */ public CompletableFuture delete(TurnContext turnContext) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } String storageKey = getStorageKey(turnContext); @@ -220,6 +224,20 @@ public JsonNode get(TurnContext turnContext) { return new ObjectMapper().valueToTree(cachedState.state); } + /** + * Gets the cached bot state instance that wraps the raw cached data for this BotState from the turn context. + * + * @param turnContext The context object for this turn. + * @return The cached bot state instance. + */ + public CachedBotState getCachedState(TurnContext turnContext) { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext cannot be null"); + } + + return turnContext.getTurnState().get(contextServiceKey); + } + /** * When overridden in a derived class, gets the key to use when reading and * writing state to and from storage. @@ -274,11 +292,15 @@ protected CompletableFuture deletePropertyValue( String propertyName ) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } if (StringUtils.isEmpty(propertyName)) { - throw new IllegalArgumentException("propertyName cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "propertyName cannot be empty" + )); } CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); @@ -300,11 +322,15 @@ protected CompletableFuture setPropertyValue( Object value ) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null." + )); } if (StringUtils.isEmpty(propertyName)) { - throw new IllegalArgumentException("propertyName cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "propertyName cannot be empty" + )); } CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); @@ -315,7 +341,7 @@ protected CompletableFuture setPropertyValue( /** * Internal cached bot state. */ - private static class CachedBotState { + public static class CachedBotState { /** * In memory cache of BotState properties. */ @@ -348,26 +374,46 @@ private static class CachedBotState { hash = computeHash(withState); } - Map getState() { + /** + * @return The Map of key value pairs which are the state. + */ + public Map getState() { return state; } + /** + * @param withState The key value pairs to set the state with. + */ void setState(Map withState) { state = withState; } + /** + * @return The hash value for the state. + */ String getHash() { return hash; } + /** + * @param witHashCode Set the hash value. + */ void setHash(String witHashCode) { hash = witHashCode; } + /** + * + * @return Boolean to tell if the state has changed. + */ boolean isChanged() { return !StringUtils.equals(hash, computeHash(state)); } + /** + * @param obj The object to compute the hash for. + * @return The computed has for the provided object. + */ String computeHash(Object obj) { if (obj == null) { return ""; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java index 4acf819ab..9b0ace749 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java @@ -153,6 +153,18 @@ void trackException( */ void trackTrace(String message, Severity severityLevel, Map properties); + /** + * Log a DialogView using the TrackPageView method on the IBotTelemetryClient if + * IBotPageViewTelemetryClient has been implemented. Alternatively log the information out via + * TrackTrace. + * + * @param dialogName The name of the dialog to log the entry / start for. + * @param properties Named string values you can use to search and classify + * events. + * @param metrics Measurements associated with this event. + */ + void trackDialogView(String dialogName, Map properties, Map metrics); + /** * Flushes the in-memory buffer and any metrics being pre-aggregated. */ diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java new file mode 100644 index 000000000..8c808fe95 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * ComponentRegistration is a signature class for discovering assets from components. + */ +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") +public class ComponentRegistration { + + private static final ConcurrentHashMap, ComponentRegistration> COMPONENTS = + new ConcurrentHashMap, ComponentRegistration>(); + + /** + * Add a component which implements registration methods. + * + * @param componentRegistration The component to add to the registration. + */ + public static void add(ComponentRegistration componentRegistration) { + COMPONENTS.put(componentRegistration.getClass(), componentRegistration); + } + + /** + * Gets list of all ComponentRegistration objects registered. + * + * @return A array of ComponentRegistration objects. + */ + public static Iterable getComponents() { + return COMPONENTS.values(); + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java index 8256ed509..db0999daa 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java @@ -29,6 +29,24 @@ public DelegatingTurnContext(TurnContext withTurnContext) { innerTurnContext = withTurnContext; } + /** + * Gets the locale on this context object. + * @return The string of locale on this context object. + */ + @Override + public String getLocale() { + return innerTurnContext.getLocale(); + } + + /** + * Set the locale on this context object. + * @param withLocale The string of locale on this context object. + */ + @Override + public void setLocale(String withLocale) { + innerTurnContext.setLocale(withLocale); + } + /** * Gets the inner context's activity. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java index f429207c2..ec89525a6 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import java.util.concurrent.CompletableFuture; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java index 54f8fb6b4..e45217f1b 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java @@ -68,4 +68,9 @@ public void trackTrace(String message, Severity severityLevel, Map properties, Map metrics) { + + } } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java index 4b5b222c3..3f699e89f 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import java.util.concurrent.CompletableFuture; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java new file mode 100644 index 000000000..6d4e7ce30 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.CompletableFuture; + +import com.nimbusds.oauth2.sdk.util.StringUtils; + +/** + * Middleware for adding an object to or registering a service with the current + * turn context. + * + * @param The typeof service to add. + */ +public class RegisterClassMiddleware implements Middleware { + private String key; + + /** + * Initializes a new instance of the RegisterClassMiddleware class. + * + * @param service The Service to register. + */ + public RegisterClassMiddleware(T service) { + this.service = service; + } + + /** + * Initializes a new instance of the RegisterClassMiddleware class. + * + * @param service The Service to register. + * @param key optional key for service object in turn state. Default is name + * of service. + */ + public RegisterClassMiddleware(T service, String key) { + this.service = service; + this.key = key; + } + + private T service; + + /** + * Gets the Service. + * + * @return The Service. + */ + public T getService() { + return service; + } + + /** + * Sets the Service. + * + * @param withService The value to set the Service to. + */ + public void setService(T withService) { + this.service = withService; + } + + @Override + /** + * Adds the associated object or service to the current turn context. + * @param turnContext The context object for this turn. + * @param next The delegate to call to continue the bot middleware pipeline. + */ + public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { + if (!StringUtils.isBlank(key)) { + turnContext.getTurnState().add(key, service); + } else { + turnContext.getTurnState().add(service); + } + return next.next(); + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java index d51ae99be..cea9ebbcf 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java @@ -50,11 +50,31 @@ static CompletableFuture traceActivity( String valueType, String label ) { - return turnContext .sendActivity(turnContext.getActivity().createTrace(name, value, valueType, label)); } + /** + * @param turnContext The turnContext. + * @param name The name of the activity. + * @return A future with the ResourceReponse. + */ + static CompletableFuture traceActivity(TurnContext turnContext, String name) { + return traceActivity(turnContext, name, null, null, null); + } + + /** + * Gets the locale on this context object. + * @return The string of locale on this context object. + */ + String getLocale(); + + /** + * Set the locale on this context object. + * @param withLocale The string of locale on this context object. + */ + void setLocale(String withLocale); + /** * Gets the bot adapter that created this context object. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index 050c311a0..2d53e94c3 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -9,6 +9,8 @@ import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.InputHints; import com.microsoft.bot.schema.ResourceResponse; +import java.util.Locale; +import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -62,6 +64,8 @@ public class TurnContextImpl implements TurnContext, AutoCloseable { */ private Boolean responded = false; + private static final String STATE_TURN_LOCALE = "turn.locale"; + /** * Creates a context object. * @@ -187,6 +191,32 @@ public boolean getResponded() { return responded; } + /** + * Gets the locale on this context object. + * @return The string of locale on this context object. + */ + @Override + public String getLocale() { + return getTurnState().get(STATE_TURN_LOCALE); + } + + /** + * Set the locale on this context object. + * @param withLocale The string of locale on this context object. + */ + @Override + public void setLocale(String withLocale) { + if (StringUtils.isEmpty(withLocale)) { + getTurnState().remove(STATE_TURN_LOCALE); + } else if ( + LocaleUtils.isAvailableLocale(new Locale.Builder().setLanguageTag(withLocale).build()) + ) { + getTurnState().replace(STATE_TURN_LOCALE, withLocale); + } else { + getTurnState().replace(STATE_TURN_LOCALE, Locale.ENGLISH.getCountry()); + } + } + /** * Sends a message activity to the sender of the incoming activity. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java index c1f5ff73e..f42e8b5f4 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java @@ -39,21 +39,30 @@ public T get(String key) throws IllegalArgumentException { } } + /** + * Returns the Services stored in the TurnContextStateCollection. + * @return the Map of String, Object pairs that contains the names and services for this collection. + */ + public Map getTurnStateServices() { + return state; + } + + /** * Get a service by type using its full type name as the key. * * @param type The type of service to be retrieved. This will use the value - * returned by Class.getSimpleName as the key. + * returned by Class.getName as the key. * @param The type of the value. * @return The service stored under the specified key. */ public T get(Class type) { - return get(type.getSimpleName()); + return get(type.getName()); } /** * Adds a value to the turn's context. - * + * * @param key The name of the value. * @param value The value to add. * @param The type of the value. @@ -76,7 +85,7 @@ public void add(String key, T value) throws IllegalArgumentException { } /** - * Add a service using its type name ({@link Class#getSimpleName()} as the key. + * Add a service using its type name ({@link Class#getName()} as the key. * * @param value The service to add. * @param The type of the value. @@ -87,12 +96,12 @@ public void add(T value) throws IllegalArgumentException { throw new IllegalArgumentException("value"); } - add(value.getClass().getSimpleName(), value); + add(value.getClass().getName(), value); } /** * Removes a value. - * + * * @param key The name of the value to remove. */ public void remove(String key) { @@ -101,7 +110,7 @@ public void remove(String key) { /** * Replaces a value. - * + * * @param key The name of the value to replace. * @param value The new value. */ @@ -110,6 +119,26 @@ public void replace(String key, Object value) { add(key, value); } + /** + * Replaces a value. + * @param value The service to add. + * @param The type of the value. + */ + public void replace(T value) { + String key = value.getClass().getName(); + replace(key, value); + } + + /** + * Returns true if this contains a mapping for the specified + * key. + * @param key The name of the value. + * @return True if the key exists. + */ + public boolean containsKey(String key) { + return state.containsKey(key); + } + /** * Auto call of {@link #close}. */ @@ -124,7 +153,7 @@ public void finalize() { /** * Close all contained {@link AutoCloseable} values. - * + * * @throws Exception Exceptions encountered by children during close. */ @Override @@ -138,4 +167,16 @@ public void close() throws Exception { } } } + + /** + * Copy the values from another TurnContextStateCollection. + * @param other The collection to copy. + */ + public void copy(TurnContextStateCollection other) { + if (other != null) { + for (String key : other.state.keySet()) { + state.put(key, other.state.get(key)); + } + } + } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 6b6d7590f..4b358b2db 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.schema.*; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java index af3ec53d8..4c68ed00f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.fasterxml.jackson.databind.JsonNode; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java index 959f73147..16ac34da1 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import org.junit.Assert; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java index a20b7bb23..a9ad40382 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java @@ -61,7 +61,7 @@ public void ScenarioWithInspectionMiddlewareOpenAttach() throws IOException { // (1) send the /INSPECT open command from the emulator to the middleware Activity openActivity = MessageFactory.text("/INSPECT open"); - TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST); + TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST, true); inspectionAdapter.processActivity(openActivity, turnContext -> { inspectionMiddleware.processCommand(turnContext).join(); return CompletableFuture.completedFuture(null); @@ -164,7 +164,7 @@ public void ScenarioWithInspectionMiddlewareOpenAttachWithMention() throws IOExc // (1) send the /INSPECT open command from the emulator to the middleware Activity openActivity = MessageFactory.text("/INSPECT open"); - TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST); + TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST, true); inspectionAdapter.processActivity(openActivity, turnContext -> { inspectionMiddleware.processCommand(turnContext).join(); return CompletableFuture.completedFuture(null); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java index d18d99dc4..809dab22c 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.connector.Attachments; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java index 32da8e319..24027f868 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.connector.Conversations; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java index 00d6fa233..f8b64a8db 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import org.junit.Test; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java index 06199d2ad..17ff38043 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java index b8fa3bec7..babf68f8c 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java index fe949002e..19c23e5c7 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import java.util.concurrent.CompletableFuture; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java index 757c51894..aa06621de 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.connector.Attachments; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java index 1dc72d537..9fd3e5287 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java index 6e1393961..daa681438 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java index 126a1aabb..8326fe099 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java index 7514e9c0d..b6be7ecc3 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java index 08740c97e..ce3581070 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.codepoetics.protonpack.collectors.CompletableFutures; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index 6153529d6..d4425747b 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -19,6 +19,8 @@ public class TestAdapter extends BotAdapter { private final Queue botReplies = new LinkedList<>(); private int nextId = 0; private ConversationReference conversationReference; + private String locale; + private boolean sendTraceActivity = false; private static class UserTokenKey { public String connectionName; @@ -54,6 +56,11 @@ public TestAdapter() { } public TestAdapter(String channelId) { + this(channelId, false); + } + + public TestAdapter(String channelId, boolean sendTraceActivity) { + this.sendTraceActivity = sendTraceActivity; setConversationReference(new ConversationReference() { { setChannelId(channelId); @@ -77,6 +84,7 @@ public TestAdapter(String channelId) { setId("Conversation1"); } }); + setLocale(this.getLocale()); } }); } @@ -108,6 +116,7 @@ public TestAdapter(ConversationReference reference) { setId("Conversation1"); } }); + setLocale(this.getLocale()); } }); } @@ -123,6 +132,35 @@ public TestAdapter use(Middleware middleware) { return this; } + /** + * Adds middleware to the adapter to register an Storage object on the turn context. + * The middleware registers the state objects on the turn context at the start of each turn. + * @param storage The storage object to register. + * @return The updated adapter. + */ + public TestAdapter useStorage(Storage storage) { + if (storage == null) { + throw new IllegalArgumentException("Storage cannot be null"); + } + return this.use(new RegisterClassMiddleware(storage)); + } + + /** + * Adds middleware to the adapter to register one or more BotState objects on the turn context. + * The middleware registers the state objects on the turn context at the start of each turn. + * @param botstates The state objects to register. + * @return The updated adapter. + */ + public TestAdapter useBotState(BotState... botstates) { + if (botstates == null) { + throw new IllegalArgumentException("botstates cannot be null"); + } + for (BotState botState : botstates) { + this.use(new RegisterClassMiddleware(botState)); + } + return this; + } + public CompletableFuture processActivity(Activity activity, BotCallbackHandler callback) { return CompletableFuture.supplyAsync(() -> { synchronized (conversationReference()) { @@ -149,6 +187,9 @@ public CompletableFuture processActivity(Activity activity, BotCallbackHan if (activity.getTimestamp() == null || activity.getTimestamp().toEpochSecond() == 0) activity.setTimestamp(OffsetDateTime.now(ZoneId.of("UTC"))); + if (activity.getLocalTimestamp() == null || activity.getLocalTimestamp().toEpochSecond() == 0) + activity.setLocalTimestamp(OffsetDateTime.now()); + return activity; }).thenCompose(activity1 -> { TurnContextImpl context = new TurnContextImpl(this, activity1); @@ -210,6 +251,12 @@ public CompletableFuture sendActivities( Thread.sleep(delayMs); } catch (InterruptedException e) { } + } else if (activity.getType() == ActivityTypes.TRACE) { + if (sendTraceActivity) { + synchronized (botReplies) { + botReplies.add(activity); + } + } } else { synchronized (botReplies) { botReplies.add(activity); @@ -452,4 +499,31 @@ public CompletableFuture> getAadTokens( ) { return CompletableFuture.completedFuture(new HashMap<>()); } + + public static ConversationReference createConversationReference(String name, String user, String bot) { + ConversationReference reference = new ConversationReference(); + reference.setChannelId("test"); + reference.setServiceUrl("https://test.com"); + reference.setConversation(new ConversationAccount(false, name, name, null, null, null, null)); + reference.setUser(new ChannelAccount(user.toLowerCase(), user.toLowerCase())); + reference.setBot(new ChannelAccount(bot.toLowerCase(), bot.toLowerCase())); + reference.setLocale("en-us"); + return reference; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public String getLocale() { + return locale; + } + + public void setSendTraceActivity(boolean sendTraceActivity) { + this.sendTraceActivity = sendTraceActivity; + } + + public boolean getSendTraceActivity() { + return sendTraceActivity; + } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java index 77e60f6a7..8672bc282 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java @@ -73,7 +73,19 @@ public TestFlow send(String userSays) throws IllegalArgumentException { } /** - * Send an activity from the user to the bot + * Creates a conversation update activity and process it the activity. + * @return A new TestFlow Object + */ + public TestFlow sendConverationUpdate() { + return new TestFlow(testTask.thenCompose(result -> { + Activity cu = Activity.createConversationUpdateActivity(); + cu.getMembersAdded().add(this.adapter.conversationReference().getUser()); + return this.adapter.processActivity(cu, callback); + }), this); + } + + /** + * Send an activity from the user to the bot. * * @param userActivity * @return diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java index d16405e2b..9160ae1f5 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder.teams; import com.microsoft.bot.builder.ActivityHandler; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java index 0da52675c..b2bae63ee 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java @@ -26,6 +26,11 @@ private Channels() { */ public static final String DIRECTLINE = "directline"; + /** + * Direct Line Speech channel. + */ + public static final String DIRECTLINESPEECH = "directlinespeech"; + /** * Email channel. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java index bd45a8675..503710b8e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java @@ -194,7 +194,9 @@ CompletableFuture replyToActivity( */ default CompletableFuture replyToActivity(Activity activity) { if (StringUtils.isEmpty(activity.getReplyToId())) { - throw new IllegalArgumentException("ReplyToId cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "ReplyToId cannot be empty" + )); } return replyToActivity( @@ -228,7 +230,7 @@ default CompletableFuture replyToActivity(Activity activity) { /** * Retrieves a single member of a conversation by ID. - * + * * @param userId The user id. * @param conversationId The conversation id. * @return The ChannelAccount for the user. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java index eadfd0a48..9a9b6cce2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java index e32abb937..ae70776f2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.AuthenticationConstants; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java index 6c21854c9..64da23a36 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java @@ -141,6 +141,11 @@ private AuthenticationConstants() { */ public static final String APPID_CLAIM = "appid"; + /** + * AppId used for creating skill claims when there is no appId and password configured. + */ + public static final String ANONYMOUS_SKILL_APPID = "AnonymousSkill"; + /** * The default clock skew in minutes. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java index 4f43761bd..b876a5d29 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.authentication; /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java index 48edc6d4b..f4e18086b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.authentication; import com.microsoft.aad.msal4j.ClientCredentialFactory; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java new file mode 100644 index 000000000..813fba475 --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.connector.authentication; + +import java.time.Duration; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +/** + * Validates JWT tokens sent to and from a Skill. + */ +@SuppressWarnings("PMD") +public final class SkillValidation { + + private SkillValidation() { + + } + + /// + /// TO SKILL FROM BOT and TO BOT FROM SKILL: Token validation parameters when + /// connecting a bot to a skill. + /// + private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = new TokenValidationParameters(true, + Stream.of( + // Auth v3.1, 1.0 token + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + // Auth v3.1, 2.0 token + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + // Auth v3.2, 1.0 token + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + // Auth v3.2, 2.0 token + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + // Auth for US Gov, 1.0 token + "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", + // Auth for US Gov, 2.0 token + "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", + // Auth for US Gov, 1.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + // Auth for US Gov, 2.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0") + .collect(Collectors.toList()), + false, // Audience validation takes place manually in code. + true, Duration.ofMinutes(5), true); + + /** + * Checks if the given list of claims represents a skill. A skill claim should + * contain: An AuthenticationConstants.VersionClaim" claim. An + * AuthenticationConstants.AudienceClaim claim. An + * AuthenticationConstants.AppIdClaim claim (v1) or an a + * AuthenticationConstants.AuthorizedParty claim (v2). And the appId claim + * should be different than the audience claim. When a channel (webchat, teams, + * etc.) invokes a bot, the + * is set to + * but when a bot calls another bot, the audience claim is set to the appId of + * the bot being invoked. The protocol supports v1 and v2 tokens: For v1 tokens, + * the AuthenticationConstants.AppIdClaim is present and set to the app Id of + * the calling bot. For v2 tokens, the AuthenticationConstants.AuthorizedParty + * is present and set to the app Id of the calling bot. + * + * @param claims A map of claims + * @return True if the list of claims is a skill claim, false if is not. + */ + public static Boolean isSkillClaim(Map claims) { + + for (Map.Entry entry : claims.entrySet()) { + if (entry.getValue() == AuthenticationConstants.ANONYMOUS_SKILL_APPID + && entry.getKey() == AuthenticationConstants.APPID_CLAIM) { + return true; + } + } + + Optional> version = claims.entrySet().stream() + .filter((x) -> x.getKey() == AuthenticationConstants.VERSION_CLAIM).findFirst(); + if (!version.isPresent()) { + // Must have a version claim. + return false; + } + + Optional> audience = claims.entrySet().stream() + .filter((x) -> x.getKey() == AuthenticationConstants.AUDIENCE_CLAIM).findFirst(); + + if (!audience.isPresent() + || AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER == audience.get().getValue()) { + // The audience is https://api.botframework.com and not an appId. + return false; + } + + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (StringUtils.isBlank(appId)) { + return false; + } + + // Skill claims must contain and app ID and the AppID must be different than the + // audience. + return appId != audience.get().getValue(); + } + +} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java index 01b11d6e7..f4d6e673a 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.rest; import com.microsoft.bot.connector.BotSignIn; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java index 97e135c97..24e15aab1 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.teams; import com.microsoft.bot.restclient.RestClient; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java index 76526b810..d0191e6d8 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains default interceptors for making HTTP requests. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java index b6b410a6d..cf2ef3499 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains the runtime classes required for AutoRest generated * clients to compile and function. To learn more about AutoRest generator, diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java index 0c47c4a00..da523673b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains classes that interfaces defining the behaviors * of the necessary components of a Rest Client. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java index 65d0fe5aa..50d38d144 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains classes that define the retry behaviors when an error * occurs during a REST call. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java index bd389f68d..ceedba31d 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains classes that handle serialization and deserialization * for the REST call payloads. diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java index 469fd7c8a..68297d38f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.schema.*; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java index 38f013ca3..459c18799 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java index 9fa4f1ec7..d99652b30 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.base.TestBase; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java index 67b93f347..de9604db5 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.auth0.jwt.algorithms.Algorithm; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java index 75fbd1c98..41351c98a 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java index 0af43a281..ac572fd29 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java index 686a16400..2757acbc9 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.RetryParams; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java index 2eb8d711b..9d8b9b4c0 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.Retry; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java index 53a40783a..b125a6169 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import org.junit.Assert; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java index 8488a2b00..60123f8b2 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java index 2341e6f87..53c1117c7 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import java.util.Map; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java index bbd40fdef..0d73b7b03 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import java.util.LinkedList; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java index 6f42a4e21..a673ec18c 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java index 1d0a801f9..2fa5fe22f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java index f8039d710..fb472db7f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonSubTypes; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java index 052d35162..6000619bb 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java index 749bc8e84..9227fb385 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java index 463ccdbef..674eaf18b 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java index 8fa997cbf..14e4cd19e 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java index e4fded082..9d5edd32f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java index 4e05f6760..99390abdc 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java index ba4cdf40f..5248d154a 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml index 1958da61d..25da5f40d 100644 --- a/libraries/bot-dialogs/pom.xml +++ b/libraries/bot-dialogs/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 @@ -53,21 +53,51 @@ org.slf4j slf4j-api - com.microsoft.azure azure-documentdb - 2.4.1 + 2.6.0 - com.microsoft.azure + com.azure azure-storage-blob - 11.0.1 + 12.8.0 + + + com.microsoft.bot + bot-integration-core + + + com.microsoft.bot + bot-builder + 4.6.0-preview8 - com.microsoft.bot bot-builder + ${project.version} + test-jar + test + + + com.google.guava + guava + 24.1-jre + + + org.javatuples + javatuples + 1.2 + + + org.apache.commons + commons-lang3 + 3.7 + + + org.yaml + snakeyaml + 1.20 @@ -98,15 +128,13 @@ - - org.apache.maven.plugins maven-pmd-plugin true - com/microsoft/bot/dialogs/** + com/microsoft/recognizers/** @@ -114,10 +142,9 @@ org.apache.maven.plugins maven-checkstyle-plugin - com/microsoft/bot/dialogs/** + com/microsoft/recognizers/** - diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java new file mode 100644 index 000000000..92022a2ae --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java @@ -0,0 +1,418 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; + + /** + * A {@link Dialog} that is composed of other dialogs. + * + * A component dialog has an inner {@link DialogSet} and {@link DialogContext} ,which provides + * an inner dialog stack that is hidden from the parent dialog. + */ + public class ComponentDialog extends DialogContainer { + + private String initialDialogId; + + /** + * The id for the persisted dialog state. + */ + public static final String PERSISTEDDIALOGSTATE = "dialogs"; + + private boolean initialized; + + /** + * Initializes a new instance of the {@link ComponentDialog} class. + * + * @param dialogId The D to assign to the new dialog within the parent dialog + * set. + */ + public ComponentDialog(String dialogId) { + super(dialogId); + } + + /** + * Called when the dialog is started and pushed onto the parent's dialog stack. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext outerDc, Object options) { + + if (outerDc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "outerDc cannot be null." + )); + } + + ensureInitialized(outerDc).join(); + + this.checkForVersionChange(outerDc).join(); + + DialogContext innerDc = this.createChildContext(outerDc); + DialogTurnResult turnResult = onBeginDialog(innerDc, options).join(); + + // Check for end of inner dialog + if (turnResult.getStatus() != DialogTurnStatus.WAITING) { + // Return result to calling dialog + DialogTurnResult result = endComponent(outerDc, turnResult.getResult()).join(); + return CompletableFuture.completedFuture(result); + } + + getTelemetryClient().trackDialogView(getId(), null, null); + + // Just signal waiting + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. If this method is *not* + * overridden, the component dialog calls the + * {@link DialogContext#continueDialog(CancellationToken)} method on its + * inner dialog context. If the inner dialog stack is empty, the + * component dialog ends, and if a {@link DialogTurnResult#result} is + * available, the component dialog uses that as its return value. + */ + @Override + public CompletableFuture continueDialog(DialogContext outerDc) { + ensureInitialized(outerDc).join(); + + this.checkForVersionChange(outerDc).join(); + + // Continue execution of inner dialog + DialogContext innerDc = this.createChildContext(outerDc); + DialogTurnResult turnResult = this.onContinueDialog(innerDc).join(); + + // Check for end of inner dialog + if (turnResult.getStatus() != DialogTurnStatus.WAITING) { + // Return to calling dialog + DialogTurnResult result = this.endComponent(outerDc, turnResult.getResult()).join(); + return CompletableFuture.completedFuture(result); + } + + // Just signal waiting + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a child dialog on the parent's dialog stack completed this turn, + * returning control to this dialog component. + * + * @param outerDc The {@link DialogContext} for the current turn of + * conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether this dialog + * is still active after this dialog turn has been processed. Generally, + * the child dialog was started with a call to + * {@link BeginDialog(DialogContext, Object)} in the parent's context. + * However, if the {@link DialogContext#replaceDialog(String, Object)} + * method is called, the logical child dialog may be different than the + * original. If this method is *not* overridden, the dialog + * automatically calls its {@link RepromptDialog(TurnContext, + * DialogInstance)} when the user replies. + */ + @Override + public CompletableFuture resumeDialog(DialogContext outerDc, DialogReason reason, + Object result) { + + ensureInitialized(outerDc).join(); + + this.checkForVersionChange(outerDc).join(); + + // Containers are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the container receiving an + // unexpected call to + // dialogResume() when the pushed on dialog ends. + // To avoid the container prematurely ending we need to implement this method + // and simply + // ask our inner dialog stack to re-prompt. + repromptDialog(outerDc.getContext(), outerDc.getActiveDialog()).join(); + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when the dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information for this dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + // Delegate to inner dialog. + DialogContext innerDc = this.createInnerDc(turnContext, instance); + innerDc.repromptDialog().join(); + + // Notify component + return onRepromptDialog(turnContext, instance); + } + + /** + * Called when the dialog is ending. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the instance of this + * component dialog on its parent's dialog stack. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * When this method is called from the parent dialog's context, the + * component dialog cancels all of the dialogs on its inner dialog stack + * before ending. + */ + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, + DialogReason reason) { + // Forward cancel to inner dialogs + if (reason == DialogReason.CANCEL_CALLED) { + DialogContext innerDc = this.createInnerDc(turnContext, instance); + innerDc.cancelAllDialogs().join(); + } + + return onEndDialog(turnContext, instance, reason); + } + + /** + * Adds a new {@link Dialog} to the component dialog and returns the updated + * component. + * + * @param dialog The dialog to add. + * + * @return The {@link ComponentDialog} after the operation is complete. + * + * The added dialog's {@link Dialog#telemetryClient} is set to the + * {@link DialogContainer#telemetryClient} of the component dialog. + */ + public ComponentDialog addDialog(Dialog dialog) { + this.getDialogs().add(dialog); + + if (this.getInitialDialogId() == null) { + this.setInitialDialogId(dialog.getId()); + } + + return this; + } + + /** + * Creates an inner {@link DialogContext} . + * + * @param dc The parent {@link DialogContext} . + * + * @return The created Dialog Context. + */ + @Override + public DialogContext createChildContext(DialogContext dc) { + return this.createInnerDc(dc, dc.getActiveDialog()); + } + + /** + * Ensures the dialog is initialized. + * + * @param outerDc The outer {@link DialogContext} . + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + protected CompletableFuture ensureInitialized(DialogContext outerDc) { + if (!this.initialized) { + this.initialized = true; + onInitialize(outerDc).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Initilizes the dialog. + * + * @param dc The {@link DialogContext} to initialize. + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + protected CompletableFuture onInitialize(DialogContext dc) { + if (this.getInitialDialogId() == null) { + Collection dialogs = getDialogs().getDialogs(); + if (dialogs.size() > 0) { + this.setInitialDialogId(dialogs.stream().findFirst().get().getId()); + } + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Called when the dialog is started and pushed onto the parent's dialog stack. + * + * @param innerDc The inner {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. By + * default, this calls the + * {@link Dialog#beginDialog(DialogContext, Object)} method of the + * component dialog's initial dialog, as defined by + * {@link InitialDialogId} . Override this method in a derived class to + * implement interrupt logic. + */ + protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { + return innerDc.beginDialog(getInitialDialogId(), options); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + * @param innerDc The inner {@link DialogContext} for the current turn of + * conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. By default, this calls the + * currently active inner dialog's + * {@link Dialog#continueDialog(DialogContext)} method. Override this + * method in a derived class to implement interrupt logic. + */ + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return innerDc.continueDialog(); + } + + /** + * Called when the dialog is ending. + * + * @param context The context Object for this turn. + * @param instance State information associated with the inner dialog stack of + * this component dialog. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * Override this method in a derived class to implement any additional + * logic that should happen at the component level, after all inner + * dialogs have been canceled. + */ + protected CompletableFuture onEndDialog(TurnContext context, DialogInstance instance, + DialogReason reason) { + return CompletableFuture.completedFuture(null); + } + + /** + * Called when the dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the inner dialog stack + * of this component dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * Override this method in a derived class to implement any additional + * logic that should happen at the component level, after the re-prompt + * operation completes for the inner dialog. + */ + protected CompletableFuture onRepromptDialog(TurnContext turnContext, DialogInstance instance) { + return CompletableFuture.completedFuture(null); + } + + /** + * Ends the component dialog in its parent's context. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * @param result Optional, value to return from the dialog component to the + * parent context. + * + * @return A task that represents the work queued to execute. + * + * If the task is successful, the result indicates that the dialog ended + * after the turn was processed by the dialog. In general, the parent + * context is the dialog or bot turn handler that started the dialog. If + * the parent is a dialog, the stack calls the parent's + * {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method to return a result to the parent dialog. If the parent dialog + * does not implement `ResumeDialog`, then the parent will end, too, and + * the result is passed to the next parent context, if one exists. The + * returned {@link DialogTurnResult} contains the return value in its + * {@link DialogTurnResult#result} property. + */ + protected CompletableFuture endComponent(DialogContext outerDc, Object result) { + return outerDc.endDialog(result); + } + + private static DialogState buildDialogState(DialogInstance instance) { + DialogState state; + + if (instance.getState().containsKey(PERSISTEDDIALOGSTATE)) { + state = (DialogState) instance.getState().get(PERSISTEDDIALOGSTATE); + } else { + state = new DialogState(); + instance.getState().put(PERSISTEDDIALOGSTATE, state); + } + + if (state.getDialogStack() == null) { + state.setDialogStack(new ArrayList()); + } + + return state; + } + + private DialogContext createInnerDc(DialogContext outerDc, DialogInstance instance) { + DialogState state = buildDialogState(instance); + + return new DialogContext(this.getDialogs(), outerDc, state); + } + + // NOTE: You should only call this if you don't have a dc to work with (such as OnResume()) + private DialogContext createInnerDc(TurnContext turnContext, DialogInstance instance) { + DialogState state = buildDialogState(instance); + + return new DialogContext(this.getDialogs(), turnContext, state); + } + /** + * Gets the id assigned to the initial dialog. + * @return the InitialDialogId value as a String. + */ + public String getInitialDialogId() { + return this.initialDialogId; + } + + /** + * Sets the id assigned to the initial dialog. + * @param withInitialDialogId The InitialDialogId value. + */ + public void setInitialDialogId(String withInitialDialogId) { + this.initialDialogId = withInitialDialogId; + } + } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index 4a38ca3a9..6aced56e8 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -1,111 +1,253 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.microsoft.bot.dialogs; +package com.microsoft.bot.dialogs; -import com.microsoft.bot.builder.BotAssert; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.NullBotTelemetryClient; import com.microsoft.bot.builder.TurnContext; - -import java.util.HashMap; import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; /** - * Base class for controls + * Base class for all dialogs. */ -public abstract class Dialog -{ - /** - * Starts the dialog. Depending on the dialog, its possible for the dialog to finish - * immediately so it's advised to check the completion Object returned by `begin()` and ensure - * that the dialog is still active before continuing. - * @param context Context for the current turn of the conversation with the user. - * @param state A state Object that the dialog will use to persist its current state. This should be an empty Object which the dialog will populate. The bot should persist this with its other conversation state for as long as the dialog is still active. - * @param options (Optional) additional options supported by the dialog. - * @return DialogCompletion result - */ - public CompletableFuture Begin(TurnContext context, HashMap state) - { - return Begin(context, state, null); - } - public CompletableFuture Begin(TurnContext context, HashMap state, HashMap options) - { - BotAssert.contextNotNull(context); - if (state == null) - throw new NullPointerException("HashMap state"); - - // Create empty dialog set and ourselves to it - // TODO: Complete - //DialogSet dialogs = new DialogSet(); - //dialogs.Add("dialog", (IDialog)this); - - // Start the control - //HashMap result = null; - - /* - // TODO Complete - - await dc.Begin("dialog", options); - */ - CompletableFuture result = null; - /* - if (dc.ActiveDialog != null) { - result = new DialogCompletion(); - result.setIsActive(true); - result.setIsCompleted(false); - } - else{ - result = new DialogCompletion(); - result.setIsActive(false); - result.setIsCompleted(true); - result.setResult(result); - } - */ - return result; - } - - /** - * Passes a users reply to the dialog for further processing.The bot should keep calling - * 'continue()' for future turns until the dialog returns a completion Object with - * 'isCompleted == true'. To cancel or interrupt the prompt simply delete the `state` Object - * being persisted. - * @param context Context for the current turn of the conversation with the user. - * @param state A state Object that was previously initialized by a call to [begin()](#begin). - * @return DialogCompletion result - */ - public CompletableFuture Continue(TurnContext context, HashMap state) - { - BotAssert.contextNotNull(context); - if (state == null) - throw new NullPointerException("HashMap"); - - // Create empty dialog set and ourselves to it - // TODO: daveta - //DialogSet dialogs = new DialogSet(); - //dialogs.Add("dialog", (IDialog)this); - - // Continue the dialog - //HashMap result = null; - CompletableFuture result = null; - /* - TODO: daveta - var dc = new DialogContext(dialogs, context, state, (r) => { result = r; }); - if (dc.ActiveDialog != null) - { - await dc.Continue(); - return dc.ActiveDialog != null - ? - new DialogCompletion { IsActive = true, IsCompleted = false } - : - new DialogCompletion { IsActive = false, IsCompleted = true, Result = result }; - } - else - { - return new DialogCompletion { IsActive = false, IsCompleted = false }; +public abstract class Dialog { + /** + * A {@link DialogTurnResult} that indicates that the current dialog is + * still active and waiting for input from the user next turn. + */ + public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(DialogTurnStatus.WAITING); + + @JsonIgnore + private BotTelemetryClient telemetryClient; + + @JsonProperty(value = "id") + private String id; + + /** + * Initializes a new instance of the Dialog class. + * @param dialogId The ID to assign to this dialog. + */ + public Dialog(String dialogId) { + id = dialogId; + telemetryClient = new NullBotTelemetryClient(); + } + + /** + * Gets id for the dialog. + * @return Id for the dialog. + */ + public String getId() { + if (StringUtils.isEmpty(id)) { + id = onComputeId(); } - */ + return id; + } - return result; + /** + * Sets id for the dialog. + * @param withId Id for the dialog. + */ + public void setId(String withId) { + id = withId; + } + /** + * Gets the {@link BotTelemetryClient} to use for logging. + * @return The BotTelemetryClient to use for logging. + */ + public BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the {@link BotTelemetryClient} to use for logging. + * @param withTelemetryClient The BotTelemetryClient to use for logging. + */ + public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { + telemetryClient = withTelemetryClient; + } + + /** + * Called when the dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture beginDialog(DialogContext dc) { + return beginDialog(dc, null); + } + + /** + * Called when the dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @param options Initial information to pass to the dialog. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public abstract CompletableFuture beginDialog(DialogContext dc, Object options); + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + *

If this method is *not* overridden, the dialog automatically ends when the user replies.

+ * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. The result may also contain a + * return value. + */ + public CompletableFuture continueDialog(DialogContext dc) { + // By default just end the current dialog. + return dc.endDialog(null); } -} + /** + * Called when a child dialog completed this turn, returning control to this dialog. + * + *

Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the + * {@link DialogContext#replaceDialog(String)} method + * is called, the logical child dialog may be different than the original.

+ * + *

If this method is *not* overridden, the dialog automatically ends when the user replies.

+ * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @return If the task is successful, the result indicates whether this dialog is still + * active after this dialog turn has been processed. + */ + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason) { + return resumeDialog(dc, reason, null); + } + + /** + * Called when a child dialog completed this turn, returning control to this dialog. + * + *

Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the + * {@link DialogContext#replaceDialog(String, Object)} method + * is called, the logical child dialog may be different than the original.

+ * + *

If this method is *not* overridden, the dialog automatically ends when the user replies.

+ * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The type of the + * value returned is dependent on the child dialog. + * @return If the task is successful, the result indicates whether this dialog is still + * active after this dialog turn has been processed. + */ + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + // By default just end the current dialog and return result to parent. + return dc.endDialog(result); + } + + /** + * Called when the dialog should re-prompt the user for input. + * @param turnContext The context object for this turn. + * @param instance State information for this dialog. + * @return A CompletableFuture representing the asynchronous operation. + */ + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + // No-op by default + return CompletableFuture.completedFuture(null); + } + + /** + * Called when the dialog is ending. + * @param turnContext The context object for this turn. + * @param instance State information associated with the instance of this dialog on the dialog stack. + * @param reason Reason why the dialog ended. + * @return A CompletableFuture representing the asynchronous operation. + */ + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + // No-op by default + return CompletableFuture.completedFuture(null); + } + + /** + * Gets a unique String which represents the version of this dialog. If the version changes + * between turns the dialog system will emit a DialogChanged event. + * @return Unique String which should only change when dialog has changed in a way that should restart the dialog. + */ + @JsonIgnore + public String getVersion() { + return id; + } + + /** + * Called when an event has been raised, using `DialogContext.emitEvent()`, by either the + * current dialog or a dialog that the current dialog started. + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return True if the event is handled by the current dialog and bubbling should stop. + */ + public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e) { + // Before bubble + return onPreBubbleEvent(dc, e) + .thenCompose(handled -> { + // Bubble as needed + if (!handled && e.shouldBubble() && dc.getParent() != null) { + return dc.getParent().emitEvent(e.getName(), e.getValue(), true, false); + } + + // just pass the handled value to the next stage + return CompletableFuture.completedFuture(handled); + }) + .thenCompose(handled -> { + if (!handled) { + // Post bubble + return onPostBubbleEvent(dc, e); + } + + return CompletableFuture.completedFuture(handled); + }); + } + + /** + * Called before an event is bubbled to its parent. + * + *

This is a good place to perform interception of an event as returning `true` will prevent + * any further bubbling of the event to the dialogs parents and will also prevent any child + * dialogs from performing their default processing.

+ * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return Whether the event is handled by the current dialog and further processing should stop. + */ + protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { + return CompletableFuture.completedFuture(false); + } + + /** + * Called after an event was bubbled to all parents and wasn't handled. + * + *

This is a good place to perform default processing logic for an event. Returning `true` will + * prevent any processing of the event by child dialogs.

+ * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return Whether the event is handled by the current dialog and further processing should stop. + */ + protected CompletableFuture onPostBubbleEvent(DialogContext dc, DialogEvent e) { + return CompletableFuture.completedFuture(false); + } + + /** + * Computes an id for the Dialog. + * @return The id. + */ + protected String onComputeId() { + return this.getClass().getName(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java deleted file mode 100644 index 826064ed2..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.dialogs; - -import java.util.HashMap; - -/** - * Result returned to the caller of one of the various stack manipulation methods and used to - * return the result from a final call to `DialogContext.end()` to the bots logic. - */ -public class DialogCompletion -{ - - - /** - * If 'true' the dialog is still active. - */ - boolean _isActive; - public void setIsActive(boolean isActive) { - this._isActive = isActive; - } - public boolean getIsActive() { - return this._isActive; - } - - /** - * If 'true' the dialog just completed and the final [result](#result) can be retrieved. - */ - boolean _isCompleted; - public void setIsCompleted(boolean isCompleted) - { - this._isCompleted = isCompleted; - } - public boolean getIsCompleted() - { - return this._isCompleted; - } - - /** - * Result returned by a dialog that was just ended.This will only be populated in certain - * cases: - * - The bot calls `dc.begin()` to start a new dialog and the dialog ends immediately. - * - The bot calls `dc.continue()` and a dialog that was active ends. - * In all cases where it's populated, [active](#active) will be `false`. - */ - HashMap _result; - public HashMap getResult() { - return _result; - } - public void setResult(HashMap result) { - this._result = result; - } -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java index c76181297..b7bccf674 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java @@ -1,50 +1,150 @@ -/* -public class DialogContainer implements IDialogContinue +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.microsoft.bot.dialogs; -{ - protected DialogSet Dialogs { get; set; } - protected string DialogId { get; set; } +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.TurnContext; +import java.util.concurrent.CompletableFuture; - public DialogContainer(string dialogId, DialogSet dialogs = null) - { - if (string.IsNullOrEmpty(dialogId)) - throw new ArgumentNullException(nameof(dialogId)); +/** + * A container for a set of Dialogs. + */ +public abstract class DialogContainer extends Dialog { + @JsonIgnore + private DialogSet dialogs = new DialogSet(); - Dialogs dialogs = (dialogs != null) ? dialogs : new DialogSet(); - DialogId = dialogId; + /** + * Creates a new instance with the default dialog id. + */ + public DialogContainer() { + super(null); } - public async Task DialogBegin(DialogContext dc, IDictionary dialogArgs = null) - { - if (dc == null) - throw new ArgumentNullException(nameof(dc)); + /** + * Creates a new instance with the default dialog id. + * @param dialogId Id of the dialog. + */ + public DialogContainer(String dialogId) { + super(dialogId); + } - // Start the controls entry point dialog. - IDictionary result = null; - var cdc = new DialogContext(this.Dialogs, dc.Context, dc.ActiveDialog.State, (r) => { result = r; }); - await cdc.Begin(DialogId, dialogArgs); - // End if the controls dialog ends. - if (cdc.ActiveDialog == null) - { - await dc.End(result); - } + /** + * Returns the Dialogs as a DialogSet. + * @return The DialogSet of Dialogs. + */ + public DialogSet getDialogs() { + return dialogs; + } + + + /** + * Sets the BotTelemetryClient to use for logging. When setting this property, + * all of the contained dialogs' BotTelemetryClient properties are also set. + * + * @param withTelemetryClient The BotTelemetryClient to use for logging. + */ + @Override + public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { + super.setTelemetryClient(withTelemetryClient != null ? withTelemetryClient : new NullBotTelemetryClient()); + dialogs.setTelemetryClient(super.getTelemetryClient()); + } + + /** + * + * Creates an inner dialog context for the containers active child. + * + * @param dc Parents dialog context. + * @return A new dialog context for the active child. + */ + public abstract DialogContext createChildContext(DialogContext dc); + + /** + * Searches the current DialogSet for a Dialog by its ID. + * + * @param dialogId ID of the dialog to search for. + * @return The dialog if found; otherwise null + */ + public Dialog findDialog(String dialogId) { + return dialogs.find(dialogId); } - public async Task DialogContinue(DialogContext dc) - { - if (dc == null) - throw new ArgumentNullException(nameof(dc)); + /** + * Called when an event has been raised, using `DialogContext.emitEvent()`, by + * either the current dialog or a dialog that the current dialog started. + * + *

+ * This override will trace version changes. + *

+ * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return True if the event is handled by the current dialog and bubbling + * should stop. + */ + @Override + public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e) { + return super.onDialogEvent(dc, e).thenCompose(handled -> { + // Trace unhandled "versionChanged" events. + if (!handled && e.getName().equals(DialogEvents.VERSION_CHANGED)) { + String traceMessage = String.format("Unhandled dialog event: {e.Name}. Active Dialog: %s", + dc.getActiveDialog().getId()); - // Continue controls dialog stack. - IDictionary result = null; - var cdc = new DialogContext(this.Dialogs, dc.Context, dc.ActiveDialog.State, (r) => { result = r; }); - await cdc.Continue(); - // End if the controls dialog ends. - if (cdc.ActiveDialog == null) - { - await dc.End(result); + dc.getDialogs().getTelemetryClient().trackTrace(traceMessage, Severity.WARNING, null); + + return TurnContext.traceActivity(dc.getContext(), traceMessage).thenApply(response -> handled); + } + + return CompletableFuture.completedFuture(handled); + }); + } + + /** + * Returns internal version identifier for this container. + * + *

+ * DialogContainers detect changes of all sub-components in the container and + * map that to an DialogChanged event. Because they do this, DialogContainers + * "hide" the internal changes and just have the .id. This isolates changes to + * the container level unless a container doesn't handle it. To support this + * DialogContainers define a protected virtual method getInternalVersion() which + * computes if this dialog or child dialogs have changed which is then examined + * via calls to checkForVersionChange(). + *

+ * + * @return version which represents the change of the internals of this + * container. + */ + protected String getInternalVersion() { + return dialogs.getVersion(); + } + + /** + * Checks to see if a containers child dialogs have changed since the current + * dialog instance was started. + * + * This should be called at the start of `beginDialog()`, `continueDialog()`, + * and `resumeDialog()`. + * + * @param dc dialog context + * @return CompletableFuture + */ + protected CompletableFuture checkForVersionChange(DialogContext dc) { + String current = dc.getActiveDialog().getVersion(); + dc.getActiveDialog().setVersion(getInternalVersion()); + + // Check for change of previously stored hash + if (current != null && !current.equals(dc.getActiveDialog().getVersion())) { + // Give bot an opportunity to handle the change. + // - If bot handles it the changeHash will have been updated as to avoid + // triggering the + // change again. + return dc.emitEvent(DialogEvents.VERSION_CHANGED, getId(), true, false).thenApply(result -> null); } + + return CompletableFuture.completedFuture(null); } } -*/ diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java index 935f353bd..3a0a51306 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java @@ -1,167 +1,579 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.dialogs; -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. -// -// Microsoft Bot Framework: http://botframework.com -// -// Bot Builder SDK GitHub: -// https://github.com/Microsoft/BotBuilder -// -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License: -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextStateCollection; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.connector.Async; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; /** - * Encapsulates a method that represents the code to execute after a result is available. - * - * The result is often a message from the user. - * - * @param T The type of the result. - * @param context The dialog context. - * @param result The result. - * @return A task that represents the code that will resume after the result is available. + * Provides context for the current state of the dialog stack. */ +public class DialogContext { + private DialogSet dialogs; + private TurnContext context; + private List stack; + private DialogContext parent; + private DialogStateManager state; + private TurnContextStateCollection services; -/* -public interface ResumeAfter -{ - CompletableFuture invoke(DialogContext contenxt, Available) -} + /** + * Initializes a new instance of the DialogContext class from the turn context. + * @param withDialogs The dialog set to create the dialog context for. + * @param withTurnContext The current turn context. + * @param withState The state property from which to retrieve the dialog context. + */ + public DialogContext(DialogSet withDialogs, TurnContext withTurnContext, DialogState withState) { + if (withDialogs == null) { + throw new IllegalArgumentException("DialogContext, DialogSet is required."); + } -public delegate Task ResumeAfter(IDialogContext context, IAwaitable result); -*/ + if (withTurnContext == null) { + throw new IllegalArgumentException("DialogContext, TurnContext is required."); + } -/** - * Encapsulate a method that represents the code to start a dialog. - * @param context The dialog context. - * @return A task that represents the start code for a dialog. - */ -//public delegate Task Start(IDialogContext context); + init(withDialogs, withTurnContext, withState); + } + /** + * Initializes a new instance of the DialogContext class from the turn context. + * @param withDialogs The dialog set to create the dialog context for. + * @param withParentDialogContext Parent dialog context. + * @param withState Current dialog state. + */ + public DialogContext( + DialogSet withDialogs, + DialogContext withParentDialogContext, + DialogState withState + ) { + if (withParentDialogContext == null) { + throw new IllegalArgumentException("DialogContext, DialogContext is required."); + } + init(withDialogs, withParentDialogContext.getContext(), withState); -/** - * The context for the execution of a dialog's conversational process. - */ -// DAVETA: TODO -// public interface DialogContext extends -public interface DialogContext { + parent = withParentDialogContext; + + // copy parent services into this DialogContext. + services.copy(getParent().getServices()); + } + + + /** + * @param withDialogs + * @param withTurnContext + * @param withState + */ + private void init(DialogSet withDialogs, TurnContext withTurnContext, DialogState withState) { + dialogs = withDialogs; + context = withTurnContext; + stack = withState.getDialogStack(); + state = new DialogStateManager(this, null); + services = new TurnContextStateCollection(); + + ObjectPath.setPathValue(context.getTurnState(), TurnPath.ACTIVITY, context.getActivity()); + } + + /** + * Gets the set of dialogs which are active for the current dialog container. + * @return The set of dialogs which are active for the current dialog container. + */ + public DialogSet getDialogs() { + return dialogs; + } + + /** + * Gets the context for the current turn of conversation. + * @return The context for the current turn of conversation. + */ + public TurnContext getContext() { + return context; + } + + /** + * Gets the current dialog stack. + * @return The current dialog stack. + */ + public List getStack() { + return stack; + } + + /** + * Gets the parent DialogContext, if any. Used when searching for the ID of a dialog to start. + * @return The parent "DialogContext, if any. Used when searching for the ID of a dialog to start. + */ + public DialogContext getParent() { + return parent; + } + + /** + * Set the parent DialogContext. + * @param withDialogContext The DialogContext to set the parent to. + */ + public void setParent(DialogContext withDialogContext) { + parent = withDialogContext; + } + + /** + * Gets dialog context for child if there is an active child. + * @return Dialog context for child if there is an active child. + */ + public DialogContext getChild() { + DialogInstance instance = getActiveDialog(); + if (instance != null) { + Dialog dialog = findDialog(instance.getId()); + if (dialog instanceof DialogContainer) { + return ((DialogContainer) dialog).createChildContext(this); + } } + return null; + } -/** - * Helper methods. - */ -/* -public static partial class Extensions -{*/ /** - * Post a message to be sent to the user, using previous messages to establish a conversation context. - * - * If the locale parameter is not set, locale of the incoming message will be used for reply. + * Gets the cached instance of the active dialog on the top of the stack or null if the stack is empty. + * @return The cached instance of the active dialog on the top of the stack or null if the stack is empty. + */ + public DialogInstance getActiveDialog() { + if (stack.size() > 0) { + return stack.get(0); + } + + return null; + } + + /** + * Gets or sets the DialogStateManager which manages view of all memory scopes. + * @return DialogStateManager with unified memory view of all memory scopes. + */ + public DialogStateManager getState() { + return state; + } + + /** + * Gets the services collection which is contextual to this dialog context. + * @return Services collection. + */ + public TurnContextStateCollection getServices() { + return services; + } + + /** + * Gets the current DialogManager for this dialogContext. + * @return The root dialogManager that was used to create this dialog context chain. + */ + public DialogManager getDialogManager() { + return getContext().getTurnState().get(DialogManager.class); + } + + /** + * Starts a new dialog and pushes it onto the dialog stack. + * @param dialogId ID of the dialog to start. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture beginDialog(String dialogId) { + return beginDialog(dialogId, null); + } + + /** + * Starts a new dialog and pushes it onto the dialog stack. + * @param dialogId ID of the dialog to start. + * @param options Optional, information to pass to the dialog being started. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture beginDialog(String dialogId, Object options) { + if (StringUtils.isEmpty(dialogId)) { + return Async.completeExceptionally(new IllegalArgumentException( + "DialogContext.beginDialog, dialogId is required" + )); + } + + // Look up dialog + Dialog dialog = findDialog(dialogId); + if (dialog == null) { + Async.completeExceptionally(new Exception(String.format( + "DialogContext.beginDialog(): A dialog with an id of '%s' wasn't found." + + " The dialog must be included in the current or parent DialogSet." + + " For example, if subclassing a ComponentDialog you can call AddDialog()" + + " within your constructor.", + dialogId + ))); + } + + // Push new instance onto stack + DialogInstance instance = new DialogInstance(dialogId, new HashMap<>()); + stack.add(0, instance); + + // Call dialog's Begin() method + return dialog.beginDialog(this, options); + } + + /** + * Helper function to simplify formatting the options for calling a prompt dialog. This helper will + * take an PromptOptions argument and then call {@link #beginDialog(String, Object)} * - * @param botToUser Communication channel to use. - * @param text The message text. - * @param locale The locale of the text. - * @return A task that represents the post operation. - */ -/* - public static async Task Post(this BotToUser botToUser, string text, string locale = null) - { - var message = botToUser.MakeMessage(); - message.Text = text; - - if (!string.IsNullOrEmpty(locale)) - { - message.Locale = locale; + * @param dialogId ID of the prompt dialog to start. + * @param options Information to pass to the prompt dialog being started. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture prompt(String dialogId, PromptOptions options) { + if (StringUtils.isEmpty(dialogId)) { + return Async.completeExceptionally(new IllegalArgumentException( + "DialogContext.prompt, dialogId is required" + )); } - await botToUser.Post(message); + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "DialogContext.prompt, PromptOptions is required" + )); + } + + return beginDialog(dialogId, options); } -*/ + /** + * Continues execution of the active dialog, if there is one, by passing the current + * DialogContext to the active dialog's {@link Dialog#continueDialog(DialogContext)} + * method. + * + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture continueDialog() { + return Async.tryCompletable(() -> { + // if we are continuing and haven't emitted the activityReceived event, emit it + // NOTE: This is backward compatible way for activity received to be fired even if + // you have legacy dialog loop + if (!getContext().getTurnState().containsKey("activityReceivedEmitted")) { + getContext().getTurnState().replace("activityReceivedEmitted", true); + + // Dispatch "activityReceived" event + // - This will queue up any interruptions. + emitEvent(DialogEvents.ACTIVITY_RECEIVED, getContext().getActivity(), true, + true + ); + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose(v -> { + if (getActiveDialog() != null) { + // Lookup dialog + Dialog dialog = this.findDialog(getActiveDialog().getId()); + if (dialog == null) { + throw new IllegalStateException(String.format( + "Failed to continue dialog. A dialog with id %s could not be found.", + getActiveDialog().getId() + )); + } + + // Continue dialog execution + return dialog.continueDialog(this); + } + + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); + }); + } /** - * Post a message and optional SSML to be sent to the user, using previous messages to establish a conversation context. + * Helper method that supplies a null result to {@link #endDialog(Object)}. * - * If the locale parameter is not set, locale of the incoming message will be used for reply. + * @return If the task is successful, the result indicates that the dialog ended after the + * turn was processed by the dialog. + */ + public CompletableFuture endDialog() { + return endDialog(null); + } + + /** + * Ends a dialog by popping it off the stack and returns an optional result to the dialog's + * parent. The parent dialog is the dialog the started the on being ended via a call to + * either {@link #beginDialog(String, Object)} or {@link #prompt(String, PromptOptions)}. The + * parent dialog will have its {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method invoked with any returned result. If the parent dialog hasn't implemented a + * {@link Dialog#resumeDialog(DialogContext dc, DialogReason reason)} method, then it will be + * automatically ended as well and the result passed to its parent. If there are no more parent + * dialogs on the stack then processing of the turn will end. + * + * @param result Optional, result to pass to the parent context. + * @return If the task is successful, the result indicates that the dialog ended after the + * turn was processed by the dialog. + */ + public CompletableFuture endDialog(Object result) { + // End the active dialog + return endActiveDialog(DialogReason.END_CALLED, result) + .thenCompose(v -> { + // Resume parent dialog + if (getActiveDialog() != null) { + // Lookup dialog + Dialog dialog = this.findDialog(getActiveDialog().getId()); + if (dialog == null) { + throw new IllegalStateException(String.format( + "DialogContext.endDialog(): Can't resume previous dialog. A dialog " + + "with an id of '%s' wasn't found.", + getActiveDialog().getId()) + ); + } + + // Return result to previous dialog + return dialog.resumeDialog(this, DialogReason.END_CALLED, result); + } + + return CompletableFuture.completedFuture( + new DialogTurnResult(DialogTurnStatus.COMPLETE, result) + ); + }); + } + + /** + * Helper method for {@link #cancelAllDialogs(boolean, String, Object)} that does not cancel + * parent dialogs or pass and event. * - * @param botToUser Communication channel to use. - * @param text The message text. - * @param speak The SSML markup for text to speech. - * @param options The options for the message. - * @param locale The locale of the text. - * @return A task that represents the post operation. - */ - /* public static async Task Say(this BotToUser botToUser, string text, string speak = null, MessageOptions options = null, string locale = null) - { - var message = botToUser.MakeMessage(); - - message.Text = text; - message.Speak = speak; - - if (!string.IsNullOrEmpty(locale)) - { - message.Locale = locale; + * @return If the task is successful, the result indicates that dialogs were canceled after the + * turn was processed by the dialog or that the stack was already empty. + */ + public CompletableFuture cancelAllDialogs() { + return cancelAllDialogs(false, null, null); + } + + /** + * Deletes any existing dialog stack thus canceling all dialogs on the stack. + * + *

In general, the parent context is the dialog or bot turn handler that started the dialog. + * If the parent is a dialog, the stack calls the parent's + * {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method to return a result to the parent dialog. If the parent dialog does not implement + * {@link Dialog#resumeDialog}, then the parent will end, too, and the result is passed to the next + * parent context.

+ * + * @param cancelParents If true the cancellation will bubble up through any parent dialogs as well. + * @param eventName The event. If null, {@link DialogEvents#CANCEL_DIALOG} is used. + * @param eventValue The event value. Can be null. + * @return If the task is successful, the result indicates that dialogs were canceled after the + * turn was processed by the dialog or that the stack was already empty. + */ + public CompletableFuture cancelAllDialogs( + boolean cancelParents, + String eventName, + Object eventValue + ) { + eventName = eventName != null ? eventName : DialogEvents.CANCEL_DIALOG; + + if (!stack.isEmpty() || getParent() != null) { + // Cancel all local and parent dialogs while checking for interception + boolean notify = false; + DialogContext dialogContext = this; + + while (dialogContext != null) { + if (!dialogContext.stack.isEmpty()) { + // Check to see if the dialog wants to handle the event + if (notify) { + Boolean eventHandled = dialogContext.emitEvent(eventName, eventValue, false, false).join(); + if (eventHandled) { + break; + } + } + + // End the active dialog + dialogContext.endActiveDialog(DialogReason.CANCEL_CALLED).join(); + } else { + dialogContext = cancelParents ? dialogContext.getParent() : null; + } + + notify = true; + } + + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.CANCELLED)); + } else { + // Stack was empty and no parent + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); } + } + + /** + * Helper method for {@link #replaceDialog(String, Object)} that passes null for options. + * @param dialogId ID of the new dialog to start. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture replaceDialog(String dialogId) { + return replaceDialog(dialogId, null); + } - if (options != null) - { - message.InputHint = options.InputHint; - message.TextFormat = options.TextFormat; - message.AttachmentLayout = options.AttachmentLayout; - message.Attachments = options.Attachments; - message.Entities = options.Entities; + /** + * Starts a new dialog and replaces on the stack the currently active dialog with the new one. + * This is particularly useful for creating loops or redirecting to another dialog. + * @param dialogId ID of the new dialog to start. + * @param options Optional, information to pass to the dialog being started. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture replaceDialog(String dialogId, Object options) { + // End the current dialog and giving the reason. + return endActiveDialog(DialogReason.REPLACE_CALLED) + .thenCompose(v -> { + ObjectPath.setPathValue(getContext().getTurnState(), "turn.__repeatDialogId", dialogId); + + // Start replacement dialog + return beginDialog(dialogId, options); + }); + } + + /** + * Calls the currently active dialog's {@link Dialog#repromptDialog(TurnContext, DialogInstance)} + * method. Used with dialogs that implement a re-prompt behavior. + * @return A task that represents the work queued to execute. + */ + public CompletableFuture repromptDialog() { + // Emit 'repromptDialog' event + return emitEvent(DialogEvents.REPROMPT_DIALOG, null, false, false) + .thenCompose(handled -> { + if (!handled && getActiveDialog() != null) { + // Lookup dialog + Dialog dialog = this.findDialog(getActiveDialog().getId()); + if (dialog == null) { + throw new IllegalStateException(String.format( + "DialogSet.repromptDialog: Can't find A dialog with an id of '%s'.", + getActiveDialog().getId() + )); + } + + // Ask dialog to re-prompt if supported + return dialog.repromptDialog(getContext(), getActiveDialog()); + } + return CompletableFuture.completedFuture(null); + }); + } + + /** + * Find the dialog id for the given context. + * @param dialogId dialog id to find. + * @return dialog with that id, or null. + */ + public Dialog findDialog(String dialogId) { + if (dialogs != null) { + Dialog dialog = dialogs.find(dialogId); + if (dialog != null) { + return dialog; + } + } + + if (getParent() != null) { + return getParent().findDialog(dialogId); + } + + return null; + } + + + /** + * @param name Name of the event to raise. + * @return CompletableFuture + */ + public CompletableFuture emitEvent(String name) { + return emitEvent(name, null, true, false); + } + + /** + * @param name Name of the event to raise. + * @param value Value to send along with the event. + * @param bubble Flag to control whether the event should be bubbled to its parent if not handled locally. + * Defaults to a value of `true`. + * @param fromLeaf Whether the event is emitted from a leaf node. + * @return CompletableFuture + */ + public CompletableFuture emitEvent(String name, Object value, boolean bubble, boolean fromLeaf) { + // Initialize event + DialogEvent dialogEvent = new DialogEvent() {{ + setBubble(bubble); + setName(name); + setValue(value); + }}; + + DialogContext dc = this; + + // Find starting dialog + if (fromLeaf) { + while (true) { + DialogContext childDc = dc.getChild(); + + if (childDc != null) { + dc = childDc; + } else { + break; + } + } + } + + // Dispatch to active dialog first + DialogInstance instance = dc.getActiveDialog(); + if (instance != null) { + Dialog dialog = dc.findDialog(instance.getId()); + + if (dialog != null) { + return dialog.onDialogEvent(dc, dialogEvent); + } } - await botToUser.Post(message); - }*/ + return CompletableFuture.completedFuture(false); + } + + /** + * Obtain the CultureInfo in DialogContext. + * @return A String representing the current locale. + */ + public String getLocale() { + return getContext() != null ? getContext().getLocale() : null; + } + /** - * Suspend the current dialog until the user has sent a message to the bot. - * @param stack The dialog stack. - * @param resume The method to resume when the message has been received. + * @param reason + * @return CompletableFuture */ -/* - public static void Wait(this IDialogStack stack, ResumeAfter resume) - { - stack.Wait(resume); + private CompletableFuture endActiveDialog(DialogReason reason) { + return endActiveDialog(reason, null); } -*/ + /** - * Call a child dialog, add it to the top of the stack and post the message to the child dialog. - * @param R The type of result expected from the child dialog. - * @param stack The dialog stack. - * @param child The child dialog. - * @param resume The method to resume when the child dialog has completed. - * @param message The message that will be posted to child dialog. - * @return A task representing the Forward operation. + * @param reason + * @param result + * @return CompletableFuture */ -/* public static async Task Forward(this IDialogStack stack, IDialog child, ResumeAfter resume, MessageActivity message) - { - await stack.Forward(child, resume, message, token); + private CompletableFuture endActiveDialog(DialogReason reason, Object result) { + DialogInstance instance = getActiveDialog(); + if (instance == null) { + return CompletableFuture.completedFuture(null); + } + + return Async.wrapBlock(() -> dialogs.find(instance.getId())) + .thenCompose(dialog -> { + if (dialog != null) { + // Notify dialog of end + return dialog.endDialog(getContext(), instance, reason); + } + + return CompletableFuture.completedFuture(null); + }) + .thenCompose(v -> { + // Pop dialog off stack + stack.remove(0); + + // set Turn.LastResult to result + ObjectPath.setPathValue(getContext().getTurnState(), TurnPath.LAST_RESULT, result); + + return CompletableFuture.completedFuture(null); + }); } -}*/ +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java new file mode 100644 index 000000000..8470110cb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path for available dialog contexts. + */ +public final class DialogContextPath { + + private DialogContextPath() { + + } + + /** + * Memory Path to dialogContext's active dialog. + */ + public static final String ACTIVEDIALOG = "dialogcontext.activeDialog"; + + /** + * Memory Path to dialogContext's parent dialog. + */ + public static final String PARENT = "dialogcontext.parent"; + + /** + * Memory Path to dialogContext's stack. + */ + public static final String STACK = "dialogContext.stack"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java new file mode 100644 index 000000000..e8c69cf7a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.List; + +/** + * Enumerate child dialog dependencies so they can be added to the containers dialogset. + */ +public interface DialogDependencies { + + /** + * Enumerate child dialog dependencies so they can be added to the containers dialogset. + * @return Dialog list + */ + List getDependencies(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java new file mode 100644 index 000000000..d6eaaee2b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Represents an event related to the "lifecycle" of the dialog. + */ +public class DialogEvent { + private boolean bubble; + private String name; + private Object value; + + /** + * Indicates whether the event will be bubbled to the parent `DialogContext` + * if not handled by the current dialog. + * @return Whether the event can be bubbled to the parent `DialogContext`. + */ + public boolean shouldBubble() { + return bubble; + } + + /** + * Sets whether the event will be bubbled to the parent `DialogContext` + * if not handled by the current dialog. + * @param withBubble Whether the event can be bubbled to the parent `DialogContext`. + */ + public void setBubble(boolean withBubble) { + bubble = withBubble; + } + + /** + * Gets name of the event being raised. + * @return Name of the event being raised. + */ + public String getName() { + return name; + } + + /** + * Sets name of the event being raised. + * @param withName Name of the event being raised. + */ + public void setName(String withName) { + name = withName; + } + + /** + * Gets optional value associated with the event. + * @return Optional value associated with the event. + */ + public Object getValue() { + return value; + } + + /** + * Sets optional value associated with the event. + * @param withValue Optional value associated with the event. + */ + public void setValue(Object withValue) { + value = withValue; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java new file mode 100644 index 000000000..d2c1dc931 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; +/** + * Represents the events related to the "lifecycle" of the dialog. + */ +public final class DialogEvents { + private DialogEvents() { } + + /// Event fired when a dialog beginDialog() is called. + public static final String BEGIN_DIALOG = "beginDialog"; + + /// Event fired when a dialog RepromptDialog is Called. + public static final String REPROMPT_DIALOG = "repromptDialog"; + + /// Event fired when a dialog is canceled. + public static final String CANCEL_DIALOG = "cancelDialog"; + + /// Event fired when an activity is received from the adapter (or a request to reprocess an activity). + public static final String ACTIVITY_RECEIVED = "activityReceived"; + + /// Event which is fired when the system has detected that deployed code has changed the execution of dialogs + /// between turns. + public static final String VERSION_CHANGED = "versionChanged"; + + /// Event fired when there was an exception thrown in the system. + public static final String ERROR = "error"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java new file mode 100644 index 000000000..55879d92f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** + * Contains state information associated with a Dialog on a dialog stack. + */ +public class DialogInstance { + @JsonProperty(value = "id") + private String id; + + @JsonProperty(value = "state") + private Map state; + + private int stackIndex; + private String version; + + /** + * Creates a DialogInstance with id and state. + */ + public DialogInstance() { + + } + + /** + * Creates a DialogInstance with id and state. + * @param withId The id + * @param withState The state. + */ + public DialogInstance(String withId, Map withState) { + id = withId; + state = withState; + } + + /** + * Gets the ID of the dialog. + * @return The dialog id. + */ + public String getId() { + return id; + } + + /** + * Sets the ID of the dialog. + * @param withId The dialog id. + */ + public void setId(String withId) { + id = withId; + } + + /** + * Gets the instance's persisted state. + * @return The instance's persisted state. + */ + public Map getState() { + return state; + } + + /** + * Sets the instance's persisted state. + * @param withState The instance's persisted state. + */ + public void setState(Map withState) { + state = withState; + } + + /** + * Gets stack index. Positive values are indexes within the current DC and negative values are + * indexes in the parent DC. + * @return Positive values are indexes within the current DC and negative values are indexes in + * the parent DC. + */ + public int getStackIndex() { + return stackIndex; + } + + /** + * Sets stack index. Positive values are indexes within the current DC and negative values are + * indexes in the parent DC. + * @param withStackIndex Positive values are indexes within the current DC and negative + * values are indexes in the parent DC. + */ + public void setStackIndex(int withStackIndex) { + stackIndex = withStackIndex; + } + + /** + * Gets version string. + * @return Unique string from the dialog this dialoginstance is tracking which is used + * to identify when a dialog has changed in way that should emit an event for changed content. + */ + public String getVersion() { + return version; + } + + /** + * Sets version string. + * @param withVersion Unique string from the dialog this dialoginstance is tracking which + * is used to identify when a dialog has changed in way that should emit + * an event for changed content. + */ + public void setVersion(String withVersion) { + version = withVersion; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java new file mode 100644 index 000000000..38965e924 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java @@ -0,0 +1,408 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.BotStateSet; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextStateCollection; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; +import com.microsoft.bot.schema.Activity; + +import org.apache.commons.lang3.NotImplementedException; + +/** + * Class which runs the dialog system. + */ +public class DialogManager { + + private final String lastAccess = "_lastAccess"; + private String rootDialogId; + private final String dialogStateProperty; + + /** + * Initializes a new instance of the + * {@link com.microsoft.bot.dialogs.DialogManager} class. + * + * @param rootDialog Root dialog to use. + * @param dialogStateProperty Alternate name for the dialogState property. + * (Default is "DialogState"). + */ + public DialogManager(Dialog rootDialog, String dialogStateProperty) { + if (rootDialog != null) { + this.setRootDialog(rootDialog); + } + + this.dialogStateProperty = dialogStateProperty != null ? dialogStateProperty : "DialogState"; + } + + private ConversationState conversationState; + + /** + * Sets the ConversationState. + * + * @param withConversationState The ConversationState. + */ + public void setConversationState(ConversationState withConversationState) { + conversationState = withConversationState; + } + + /** + * Gets the ConversationState. + * + * @return The ConversationState. + */ + public ConversationState getConversationState() { + return conversationState; + } + + private UserState userState; + + /** + * Gets the UserState. + * + * @return UserState. + */ + public UserState getUserState() { + return this.userState; + } + + /** + * Sets the UserState. + * + * @param userState UserState. + */ + public void setUserState(UserState userState) { + this.userState = userState; + } + + private TurnContextStateCollection initialTurnState = new TurnContextStateCollection(); + + /** + * Gets InitialTurnState collection to copy into the TurnState on every turn. + * + * @return TurnState. + */ + public TurnContextStateCollection getInitialTurnState() { + return initialTurnState; + } + + /** + * Gets the Root Dialog. + * + * @return the Root Dialog. + */ + public Dialog getRootDialog() { + if (rootDialogId != null) { + return this.getDialogs().find(rootDialogId); + } else { + return null; + } + + } + + /** + * Sets root dialog to use to start conversation. + * + * @param dialog Root dialog to use to start conversation. + */ + public void setRootDialog(Dialog dialog) { + setDialogs(new DialogSet()); + if (dialog != null) { + rootDialogId = dialog.getId(); + getDialogs().setTelemetryClient(dialog.getTelemetryClient()); + getDialogs().add(dialog); + registerContainerDialogs(dialog, false); + } else { + rootDialogId = null; + } + } + + @JsonIgnore + private DialogSet dialogs = new DialogSet(); + + /** + * Returns the DialogSet. + * + * @return The DialogSet. + */ + public DialogSet getDialogs() { + return dialogs; + } + + /** + * Set the DialogSet. + * + * @param withDialogSet The DialogSet being provided. + */ + public void setDialogs(DialogSet withDialogSet) { + dialogs = withDialogSet; + } + + private DialogStateManagerConfiguration stateManagerConfiguration; + + /** + * Gets the DialogStateManagerConfiguration. + * + * @return The DialogStateManagerConfiguration. + */ + public DialogStateManagerConfiguration getStateManagerConfiguration() { + return this.stateManagerConfiguration; + } + + /** + * Sets the DialogStateManagerConfiguration. + * + * @param withStateManagerConfiguration The DialogStateManagerConfiguration to + * set from. + */ + public void setStateManagerConfiguration(DialogStateManagerConfiguration withStateManagerConfiguration) { + this.stateManagerConfiguration = withStateManagerConfiguration; + } + + private Integer expireAfter; + + /** + * Gets the (optinal) number of milliseconds to expire the bot's state after. + * + * @return Number of milliseconds. + */ + public Integer getExpireAfter() { + return this.expireAfter; + } + + /** + * Sets the (optional) number of milliseconds to expire the bot's state after. + * + * @param withExpireAfter Number of milliseconds. + */ + public void setExpireAfter(Integer withExpireAfter) { + this.expireAfter = withExpireAfter; + } + + /** + * Runs dialog system in the context of an ITurnContext. + * + * @param context Turn Context + * @return result of runnign the logic against the activity. + */ + public CompletableFuture onTurn(TurnContext context) { + BotStateSet botStateSet = new BotStateSet(); + + // Preload TurnState with DM TurnState. + initialTurnState.getTurnStateServices().forEach((key, value) -> { + context.getTurnState().add(key, value); + }); + + // register DialogManager with TurnState. + context.getTurnState().replace(this); + + if (conversationState == null) { + ConversationState cState = context.getTurnState().get(ConversationState.class); + if (cState != null) { + conversationState = cState; + } else { + return Async.completeExceptionally(new IllegalStateException( + String.format("Unable to get an instance of %s from turnContext.", + ConversationState.class.toString()) + )); + } + } else { + context.getTurnState().replace(conversationState); + } + + botStateSet.add(conversationState); + + if (userState == null) { + userState = context.getTurnState().get(UserState.class); + } else { + context.getTurnState().replace(userState); + } + + if (userState != null) { + botStateSet.add(userState); + } + + // create property accessors + StatePropertyAccessor lastAccessProperty = conversationState.createProperty(lastAccess); + + OffsetDateTime lastAccessed = lastAccessProperty.get(context, () -> { + return OffsetDateTime.now(ZoneId.of("UTC")); + }).join(); + + // Check for expired conversation + if (expireAfter != null && (OffsetDateTime.now(ZoneId.of("UTC")).toInstant().toEpochMilli() + - lastAccessed.toInstant().toEpochMilli()) >= expireAfter) { + conversationState.clearState(context).join(); + } + + lastAccessed = OffsetDateTime.now(ZoneId.of("UTC")); + lastAccessProperty.set(context, lastAccessed).join(); + + // get dialog stack + StatePropertyAccessor dialogsProperty = conversationState.createProperty(dialogStateProperty); + + DialogState dialogState = dialogsProperty.get(context, DialogState::new).join(); + + // Create DialogContext + DialogContext dc = new DialogContext(dialogs, context, dialogState); + + dc.getServices().getTurnStateServices().forEach((key, value) -> { + dc.getServices().add(key, value); + }); + + // map TurnState into root dialog context.services + context.getTurnState().getTurnStateServices().forEach((key, value) -> { + dc.getServices().add(key, value); + }); + + // get the DialogStateManager configuration + DialogStateManager dialogStateManager = new DialogStateManager(dc, stateManagerConfiguration); + dialogStateManager.loadAllScopesAsync().join(); + dc.getContext().getTurnState().add(dialogStateManager); + + DialogTurnResult turnResult = null; + + // Loop as long as we are getting valid OnError handled we should continue + // executing the + // actions for the turn. + // NOTE: We loop around this block because each pass through we either complete + // the turn and + // break out of the loop + // or we have had an exception AND there was an OnError action which captured + // the error. + // We need to continue the turn based on the actions the OnError handler + // introduced. + Boolean endOfTurn = false; + while (!endOfTurn) { + try { + ClaimsIdentity claimIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (claimIdentity != null && SkillValidation.isSkillClaim(claimIdentity.claims())) { + // The bot is running as a skill. + turnResult = handleSkillOnTurn().join(); + } else { + // The bot is running as root bot. + turnResult = handleBotOnTurn(dc).join(); + } + + // turn successfully completed, break the loop + endOfTurn = true; + } catch (Exception err) { + // fire error event, bubbling from the leaf. + Boolean handled = dc.emitEvent(DialogEvents.ERROR, err, true, true).join(); + + if (!handled) { + // error was NOT handled, throw the exception and end the turn. (This will + // trigger + // the Adapter.OnError handler and end the entire dialog stack) + return Async.completeExceptionally(new RuntimeException(err)); + } + } + } + + // save all state scopes to their respective botState locations. + dialogStateManager.saveAllChanges(); + + // save BotState changes + botStateSet.saveAllChanges(dc.getContext(), false); + + DialogManagerResult result = new DialogManagerResult(); + result.setTurnResult(turnResult); + return CompletableFuture.completedFuture(result); + } + + /// + /// Helper to send a trace activity with a memory snapshot of the active dialog + /// DC. + /// + private static CompletableFuture sendStateSnapshotTrace(DialogContext dc, String traceLabel) { + // send trace of memory + JsonNode snapshot = getActiveDialogContext(dc).getState().getMemorySnapshot(); + Activity traceActivity = (Activity) Activity.createTraceActivity("Bot State", + "https://www.botframework.com/schemas/botState", snapshot, traceLabel); + dc.getContext().sendActivity(traceActivity).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * Recursively walk up the DC stack to find the active DC. + */ + private static DialogContext getActiveDialogContext(DialogContext dialogContext) { + DialogContext child = dialogContext.getChild(); + if (child == null) { + return dialogContext; + } else { + return getActiveDialogContext(child); + } + } + + @SuppressWarnings({"TodoComment"}) + //TODO: Add Skills support here + private CompletableFuture handleSkillOnTurn() { + return Async.completeExceptionally(new NotImplementedException( + "Skills are not implemented in this release" + )); + } + + /** + * Recursively traverses the Dialog tree and registers instances of + * DialogContainer in the DialogSet for this + * instance. + * + * @param dialog Root of the Dialog subtree to iterate and register + * containers from. + * @param registerRoot Whether to register the root of the subtree. + */ + private void registerContainerDialogs(Dialog dialog, Boolean registerRoot) { + if (dialog instanceof DialogContainer) { + if (registerRoot) { + getDialogs().add(dialog); + } + + Collection dlogs = ((DialogContainer) dialog).getDialogs().getDialogs(); + + for (Dialog dlg : dlogs) { + registerContainerDialogs(dlg, true); + } + } + } + + private CompletableFuture handleBotOnTurn(DialogContext dc) { + DialogTurnResult turnResult; + + // the bot is running as a root bot. + if (dc.getActiveDialog() == null) { + // start root dialog + turnResult = dc.beginDialog(rootDialogId).join(); + } else { + // Continue execution + // - This will apply any queued up interruptions and execute the current/next + // step(s). + turnResult = dc.continueDialog().join(); + + if (turnResult.getStatus() == DialogTurnStatus.EMPTY) { + // restart root dialog + turnResult = dc.beginDialog(rootDialogId).join(); + } + } + + sendStateSnapshotTrace(dc, "BotState").join(); + + return CompletableFuture.completedFuture(turnResult); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java new file mode 100644 index 000000000..d73d91c28 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.microsoft.bot.schema.Activity; + +/** + * Represents the result of the Dialog Manager turn. + */ +public class DialogManagerResult { + /** + * The result returned to the caller. + */ + private DialogTurnResult turnResult; + + /** + * The array of resulting activities. + */ + private Activity[] activities; + + /** + * The resulting new state. + */ + private PersistedState newState; + + /** + * @return DialogTurnResult + */ + public DialogTurnResult getTurnResult() { + return this.turnResult; + } + + /** + * @param withTurnResult Sets the turnResult. + */ + public void setTurnResult(DialogTurnResult withTurnResult) { + this.turnResult = withTurnResult; + } + + /** + * @return Activity[] + */ + public Activity[] getActivities() { + return this.activities; + } + + /** + * @param withActivities Sets the activites. + */ + public void setActivities(Activity[] withActivities) { + this.activities = withActivities; + } + + /** + * @return PersistedState + */ + public PersistedState getNewState() { + return this.newState; + } + + /** + * @param withNewState sets the newState. + */ + public void setNewState(PersistedState withNewState) { + this.newState = withNewState; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java new file mode 100644 index 000000000..8827644f7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path for available dialogs. + */ +public final class DialogPath { + + private DialogPath() { + + } + + /** + * Counter of emitted events. + */ + public static final String EVENTCOUNTER = "dialog.eventCounter"; + + /** + * Currently expected properties. + */ + public static final String EXPECTEDPROPERTIES = "dialog.expectedProperties"; + + /** + * Default operation to use for entities where there is no identified operation entity. + */ + public static final String DEFAULTOPERATION = "dialog.defaultOperation"; + + /** + * Last surfaced entity ambiguity event. + */ + public static final String LASTEVENT = "dialog.lastEvent"; + + /** + * Currently required properties. + */ + public static final String REQUIREDPROPERTIES = "dialog.requiredProperties"; + + /** + * Number of retries for the current Ask. + */ + public static final String RETRIES = "dialog.retries"; + + /** + * Last intent. + */ + public static final String LASTINTENT = "dialog.lastIntent"; + + /** + * Last trigger event: defined in FormEvent, ask, clarifyEntity etc. + */ + public static final String LASTTRIGGEREVENT = "dialog.lastTriggerEvent"; + + /** + * Utility function to get just the property name without the memory scope prefix. + * @param property Memory scope property path. + * @return Name of the property without the prefix. + */ + public static String getPropertyName(String property) { + return property.replace("dialog.", ""); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java new file mode 100644 index 000000000..efe2a152d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Indicates in which a dialog-related method is being called. + */ +public enum DialogReason { + /// A dialog was started. + BEGIN_CALLED, + + /// A dialog was continued. + CONTINUE_CALLED, + + /// A dialog was ended normally. + END_CALLED, + + /// A dialog was ending because it was replaced. + REPLACE_CALLED, + + /// A dialog was canceled. + CANCEL_CALLED, + + /// A preceding step of the dialog was skipped. + NEXT_CALLED +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java new file mode 100644 index 000000000..c5cbb4b61 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.hash.Hashing; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.collections4.map.HashedMap; +import org.apache.commons.lang3.StringUtils; + +/** + * A collection of Dialog objects that can all call each other. + */ +public class DialogSet { + private Map dialogs = new HashedMap<>(); + private StatePropertyAccessor dialogState; + @JsonIgnore + private BotTelemetryClient telemetryClient; + private String version; + + /** + * Initializes a new instance of the DialogSet class. + * + *

+ * To start and control the dialogs in this dialog set, create a DialogContext + * and use its methods to start, continue, or end dialogs. To create a dialog + * context, call createContext(TurnContext). + *

+ * + * @param withDialogState The state property accessor with which to manage the + * stack for this dialog set. + */ + public DialogSet(StatePropertyAccessor withDialogState) { + if (withDialogState == null) { + throw new IllegalArgumentException("DialogState is required"); + } + dialogState = withDialogState; + telemetryClient = new NullBotTelemetryClient(); + } + + /** + * Creates a DialogSet without state. + */ + public DialogSet() { + dialogState = null; + telemetryClient = new NullBotTelemetryClient(); + } + + /** + * Gets the BotTelemetryClient to use for logging. + * + * @return The BotTelemetryClient to use for logging. + */ + public BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the BotTelemetryClient to use for logging. + * + *

+ * When this property is set, it sets the Dialog.TelemetryClient of each dialog + * in the set to the new value. + *

+ * + * @param withBotTelemetryClient The BotTelemetryClient to use for logging. + */ + public void setTelemetryClient(BotTelemetryClient withBotTelemetryClient) { + telemetryClient = withBotTelemetryClient != null ? withBotTelemetryClient : new NullBotTelemetryClient(); + + for (Dialog dialog : dialogs.values()) { + dialog.setTelemetryClient(telemetryClient); + } + } + + /** + * Gets a unique string which represents the combined versions of all dialogs in + * this dialogset. + * + * @return Version will change when any of the child dialogs version changes. + */ + public String getVersion() { + if (version == null) { + StringBuilder sb = new StringBuilder(); + for (Dialog dialog : dialogs.values()) { + String v = dialog.getVersion(); + if (!StringUtils.isEmpty(v)) { + sb.append(v); + } + } + + version = Hashing.sha256().hashString(sb.toString(), StandardCharsets.UTF_8).toString(); + } + + return version; + } + + /** + * Adds a new dialog to the set and returns the set to allow fluent chaining. If + * the Dialog.Id being added already exists in the set, the dialogs id will be + * updated to include a suffix which makes it unique. So adding 2 dialogs named + * "duplicate" to the set would result in the first one having an id of + * "duplicate" and the second one having an id of "duplicate2". + * + * @param dialog The dialog to add. The added dialog's Dialog.TelemetryClient is + * set to the BotTelemetryClient of the dialog set. + * @return The dialog set after the operation is complete. + */ + public DialogSet add(Dialog dialog) { + // Ensure new version hash is computed + version = null; + + if (dialog == null) { + throw new IllegalArgumentException("Dialog is required"); + } + + if (dialogs.containsKey(dialog.getId())) { + // If we are trying to add the same exact instance, it's not a name collision. + // No operation required since the instance is already in the dialog set. + if (dialogs.get(dialog.getId()) == dialog) { + return this; + } + + // If we are adding a new dialog with a conflicting name, add a suffix to avoid + // dialog name collisions. + int nextSuffix = 2; + + while (true) { + String suffixId = dialog.getId() + nextSuffix; + + if (!dialogs.containsKey(suffixId)) { + dialog.setId(suffixId); + break; + } + + nextSuffix++; + } + } + + dialog.setTelemetryClient(telemetryClient); + dialogs.put(dialog.getId(), dialog); + + // Automatically add any dependencies the dialog might have + if (dialog instanceof DialogDependencies) { + for (Dialog dependencyDialog : ((DialogDependencies) dialog).getDependencies()) { + add(dependencyDialog); + } + } + + return this; + } + + /** + * Creates a DialogContext which can be used to work with the dialogs in the + * DialogSet. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @return A CompletableFuture representing the asynchronous operation. + */ + public CompletableFuture createContext(TurnContext turnContext) { + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext is required" + )); + } + + if (dialogState == null) { + // Note: This shouldn't ever trigger, as the _dialogState is set in the + // constructor + // and validated there. + return Async.completeExceptionally(new IllegalStateException( + "DialogSet.createContext(): DialogSet created with a null StatePropertyAccessor." + )); + } + + // Load/initialize dialog state + return dialogState.get(turnContext, DialogState::new) + .thenApply(state -> new DialogContext(this, turnContext, state)); + } + + /** + * Searches the current DialogSet for a Dialog by its ID. + * + * @param dialogId ID of the dialog to search for. + * @return The dialog if found; otherwise null + */ + public Dialog find(String dialogId) { + if (StringUtils.isEmpty(dialogId)) { + throw new IllegalArgumentException("DialogSet.find, dialogId is required"); + } + + return dialogs.get(dialogId); + } + + /** + * Returns a collection of Dialogs in this DialogSet. + * + * @return The Dialogs in this DialogSet. + */ + public Collection getDialogs() { + return dialogs.values(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java new file mode 100644 index 000000000..29d1fdb01 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; + +/** + * Contains state information for the dialog stack. + */ +public class DialogState { + @JsonProperty(value = "dialogStack") + private List dialogStack = new ArrayList(); + + /** + * Initializes a new instance of the class with an empty stack. + */ + public DialogState() { + this(null); + } + + /** + * Initializes a new instance of the class. + * @param withDialogStack The state information to initialize the stack with. + */ + public DialogState(List withDialogStack) { + dialogStack = withDialogStack != null ? withDialogStack : new ArrayList(); + } + + /** + * Gets the state information for a dialog stack. + * @return State information for a dialog stack. + */ + public List getDialogStack() { + return dialogStack; + } + + /** + * Sets the state information for a dialog stack. + * @param withDialogStack State information for a dialog stack. + */ + public void setDialogStack(List withDialogStack) { + dialogStack = withDialogStack; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java new file mode 100644 index 000000000..484d01111 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Result returned to the caller of one of the various stack manipulation methods. + */ +public class DialogTurnResult { + private DialogTurnStatus status; + private Object result; + private boolean parentEnded; + + /** + * Creates a DialogTurnResult with a status. + * @param withStatus The dialog status. + */ + public DialogTurnResult(DialogTurnStatus withStatus) { + this(withStatus, null); + } + + /** + * Creates a DialogTurnResult with a status and result. + * @param withStatus The dialog status. + * @param withResult The result. + */ + public DialogTurnResult(DialogTurnStatus withStatus, Object withResult) { + status = withStatus; + result = withResult; + } + + /** + * Gets the current status of the stack. + * @return The current status of the stack. + */ + public DialogTurnStatus getStatus() { + return status; + } + + /** + * Sets the current status of the stack. + * @param withStatus The current status of the stack. + */ + public void setStatus(DialogTurnStatus withStatus) { + status = withStatus; + } + + /** + * Gets or sets the result returned by a dialog that was just ended. + * + *

This will only be populated in certain cases: + *
- The bot calls `DialogContext.BeginDialogAsync()` to start a new dialog and the dialog + * ends immediately. + *
- The bot calls `DialogContext.ContinueDialogAsync()` and a dialog that was active ends.

+ * + *

In all cases where it's populated, will be `null`.

+ * @return The result returned by a dialog that was just ended. + */ + public Object getResult() { + return result; + } + + /** + * Sets the result returned by a dialog that was just ended. + * @param withResult The result returned by a dialog that was just ended. + */ + public void setResult(Object withResult) { + result = withResult; + } + + /** + * Indicates whether a DialogCommand has ended its parent container and the parent should + * not perform any further processing. + * @return Whether a DialogCommand has ended its parent container and the parent should + * not perform any further processing. + */ + public boolean hasParentEnded() { + return parentEnded; + } + + /** + * Sets whether a DialogCommand has ended its parent container and the parent should + * not perform any further processing. + * @param withParentEnded Whether a DialogCommand has ended its parent container and the + * parent should not perform any further processing. + */ + public void setParentEnded(boolean withParentEnded) { + parentEnded = withParentEnded; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java new file mode 100644 index 000000000..1a5a6a84d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Result returned to the caller of one of the various stack manipulation methods. + */ +public enum DialogTurnStatus { + /// Indicates that there is currently nothing on the dialog stack. + EMPTY, + + /// Indicates that the dialog on top is waiting for a response from the user. + WAITING, + + /// Indicates that a dialog completed successfully, the result is available, and no child + /// dialogs to the current context are on the dialog stack. + COMPLETE, + + /// Indicates that the dialog was canceled, and no child + /// dialogs to the current context are on the dialog stack. + CANCELLED, + + /// Current dialog completed successfully, but turn should end. + COMPLETEANDWAIT, +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java new file mode 100644 index 000000000..750bd4cb3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.bot.builder.ComponentRegistration; +import com.microsoft.bot.dialogs.memory.ComponentMemoryScopes; +import com.microsoft.bot.dialogs.memory.ComponentPathResolvers; +import com.microsoft.bot.dialogs.memory.PathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.PercentPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtAtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.HashPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.DollarPathResolver; +import com.microsoft.bot.dialogs.memory.scopes.ClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ConversationMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogContextMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.SettingsMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ThisMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.TurnMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.UserMemoryScope; + +/** + * Makes Dialogs components available to the system registering functionality. + */ +public class DialogsComponentRegistration extends ComponentRegistration + implements ComponentMemoryScopes, ComponentPathResolvers { + + /** + * Gets the Dialogs Path Resolvers. + */ + @Override + public Iterable getPathResolvers() { + List listToReturn = new ArrayList(); + listToReturn.add((PathResolver) new DollarPathResolver()); + listToReturn.add((PathResolver) new HashPathResolver()); + listToReturn.add((PathResolver) new AtAtPathResolver()); + listToReturn.add((PathResolver) new AtPathResolver()); + listToReturn.add((PathResolver) new PercentPathResolver()); + return listToReturn; + } + + /** + * Gets the Dialogs Memory Scopes. + */ + @Override + public Iterable getMemoryScopes() { + List listToReturn = new ArrayList(); + listToReturn.add(new TurnMemoryScope()); + listToReturn.add(new SettingsMemoryScope()); + listToReturn.add(new DialogMemoryScope()); + listToReturn.add(new DialogContextMemoryScope()); + listToReturn.add(new DialogClassMemoryScope()); + listToReturn.add(new ClassMemoryScope()); + listToReturn.add(new ThisMemoryScope()); + listToReturn.add(new ConversationMemoryScope()); + listToReturn.add(new UserMemoryScope()); + return listToReturn; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java deleted file mode 100644 index 366a69d0c..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.microsoft.bot.dialogs; - -// TODO: daveta remove this - not sure where this came from -/** - * Interface for all Dialog objects that can be added to a `DialogSet`. The dialog should generally - * be a singleton and added to a dialog set using `DialogSet.add()` at which point it will be - * assigned a unique ID. - */ -public interface IDialog -{ - /** - * Method called when a new dialog has been pushed onto the stack and is being activated. - * @param dc The dialog context for the current turn of conversation. - * @param dialogArgs (Optional) arguments that were passed to the dialog during `begin()` call that started the instance. - */ - //CompleteableFuture DialogBegin(DialogContext dc, IDictionary dialogArgs = null); - //CompleteableFuture DialogBegin(DialogContext dc, HashMap dialogArgs); - //CompleteableFuture DialogBegin(DialogContext dc); -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java deleted file mode 100644 index bf6c30de2..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.microsoft.bot.dialogs; - - -import java.util.concurrent.CompletableFuture; - -/** - * Interface Dialog objects that can be continued. - */ -public interface IDialogContinue extends IDialog -{ - /** - * Method called when an instance of the dialog is the "current" dialog and the - * user replies with a new activity. The dialog will generally continue to receive the users - * replies until it calls either `DialogSet.end()` or `DialogSet.begin()`. - * If this method is NOT implemented then the dialog will automatically be ended when the user replies. - * @param dc The dialog context for the current turn of conversation. - */ - CompletableFuture DialogContinue(DialogContext dc); -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java deleted file mode 100644 index 96db67810..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.microsoft.bot.dialogs; - -import com.microsoft.bot.schema.AttachmentLayoutTypes; -import com.microsoft.bot.schema.TextFormatTypes; - -/** - * Optional message properties that can be sent {@link Extensions.Say(BotToUser, String MessageOptions,)} - */ -public class MessageOptions -{ - public MessageOptions() - { - this.setTextFormat(TextFormatTypes.MARKDOWN.toString()); - this.setAttachmentLayout(AttachmentLayoutTypes.LIST.toString()); - // this.Attachments = new ArrayList(); - // this.Entities = new ArrayList(); - } - - /** - * Indicates whether the bot is accepting, expecting, or ignoring input - */ - //public string InputHint { get; set; } - - /** - * Format of text fields [plain|markdown] Default:markdown - */ - String textFormat; - public String getTextFormat() { - return this.textFormat; - } - public void setTextFormat(String textFormat) { - this.textFormat = textFormat; - } - - - - /** - * Hint for how to deal with multiple attachments: [list|carousel] Default:list - */ - String attachmentLayout; - public String getAttachmentLayout() { - return this.attachmentLayout; - } - public void setAttachmentLayout(String attachmentLayout) { - this.attachmentLayout = attachmentLayout; - } - - /** - * Attachments - */ - //public IList Attachments { get; set; } - - /** - * Collection of Entity objects, each of which contains metadata about this activity. Each Entity object is typed. - */ - //public IList Entities { get; set; } -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java new file mode 100644 index 000000000..61e3c31e3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java @@ -0,0 +1,865 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.schema.Serialization; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * Generic Arraylist of Object. + */ +class Segments extends ArrayList { + + /** + * Returns the first item in the collection. + * @return the first object. + */ + public Object first() { + return get(0); + } + + /** + * Returns the last item in the collection. + * @return the last object. + */ + public Object last() { + return get(size() - 1); + } + + /** + * Gets the SegmentType at the specified index. + * @param index Index of the requested segment. + * @return The SegmentType of item at the requested index. + */ + public SegmentType getSegment(int index) { + return new SegmentType(get(index)); + } +} + +/** + * A class wraps an Object and can assist in determining if it's an integer. + */ +@SuppressWarnings("checkstyle:VisibilityModifier") +class SegmentType { + + public boolean isInt; + public int intValue; + public Segments segmentsValue; + public String stringValue; + + /** + * + * @param value The object to create a SegmentType for. + */ + SegmentType(Object value) { + try { + intValue = Integer.parseInt((String) value); + isInt = true; + } catch (NumberFormatException e) { + isInt = false; + } + + if (!isInt) { + if (value instanceof Segments) { + segmentsValue = (Segments) value; + } else { + stringValue = (String) value; + } + } + } +} + +/** + * Helper methods for working with dynamic json objects. + */ +public final class ObjectPath { + private ObjectPath() { } + + /** + * Does an Object have a subpath. + * @param obj Object. + * @param path path to evaluate. + * @return true if the path is there. + */ + public static boolean hasValue(Object obj, String path) { + return tryGetPathValue(obj, path, Object.class) != null; + } + + /** + * Get the value for a path relative to an Object. + * @param type to return. + * @param obj Object to start with. + * @param path path to evaluate. + * @param valueType Type of T + * @return value or default(T). + */ + @SuppressWarnings("checkstyle:InnerAssignment") + public static T getPathValue(Object obj, String path, Class valueType) { + T value; + if ((value = tryGetPathValue(obj, path, valueType)) != null) { + return value; + } + + throw new IllegalArgumentException(path); + } + + /** + * Get the value for a path relative to an Object. + * @param type to return. + * @param obj Object to start with. + * @param path path to evaluate. + * @param valueType type of T + * @param defaultValue default value to use if any part of the path is missing. + * @return value or default(T). + */ + @SuppressWarnings("checkstyle:InnerAssignment") + public static T getPathValue(Object obj, String path, Class valueType, T defaultValue) { + T value; + if ((value = tryGetPathValue(obj, path, valueType)) != null) { + return value; + } + + return defaultValue; + } + + /** + * Get the value for a path relative to an Object. + * @param type to return. + * @param obj Object to start with. + * @param path path to evaluate. + * @param valueType value for the path. + * @return true if successful. + */ + public static T tryGetPathValue(Object obj, String path, Class valueType) { + if (obj == null || path == null) { + return null; + } + + if (path.length() == 0) { + return mapValueTo(obj, valueType); + } + + Segments segments = tryResolvePath(obj, path); + if (segments == null) { + return null; + } + + Object result = resolveSegments(obj, segments); + if (result == null) { + return null; + } + + // look to see if it's ExpressionProperty and bind it if it is + // NOTE: this bit of duck typing keeps us from adding dependency between adaptiveExpressions and Dialogs. + /* + if (result.GetType().GetProperty("ExpressionText") != null) + { + var method = result.GetType().GetMethod("GetValue", new[] { typeof(Object) }); + if (method != null) + { + result = method.Invoke(result, new[] { obj }); + } + } + */ + + return mapValueTo(result, valueType); + } + + /** + * Given an Object evaluate a path to set the value. + * @param obj Object to start with. + * @param path path to evaluate. + * @param value value to store. + */ + public static void setPathValue(Object obj, String path, Object value) { + setPathValue(obj, path, value, true); + } + + /** + * Given an Object evaluate a path to set the value. + * @param obj Object to start with. + * @param path path to evaluate. + * @param value value to store. + * @param json if true, sets the value as primitive JSON Objects. + */ + public static void setPathValue(Object obj, String path, Object value, boolean json) { + Segments segments = tryResolvePath(obj, path); + if (segments == null) { + return; + } + + Object current = obj; + for (int i = 0; i < segments.size() - 1; i++) { + SegmentType segment = segments.getSegment(i); + Object next; + if (segment.isInt) { + if (((Segments) current).size() <= segment.intValue) { + // TODO make sure growBy is correct + // Expand array to index + int growBy = segment.intValue - ((Segments) current).size(); + ((ArrayNode) current).add(growBy); + } + next = ((ArrayNode) current).get(segment.intValue); + } else { + next = getObjectProperty(current, segment.stringValue); + if (next == null) { + // Create Object or array base on next segment + SegmentType nextSegment = new SegmentType(segments.get(i + 1)); + if (nextSegment.stringValue != null) { + setObjectSegment(current, segment.stringValue, JsonNodeFactory.instance.objectNode()); + } else { + setObjectSegment(current, segment.stringValue, JsonNodeFactory.instance.arrayNode()); + } + next = getObjectProperty(current, segment.stringValue); + } + } + + current = next; + } + + Object lastSegment = segments.last(); + setObjectSegment(current, lastSegment, value, json); + } + + /** + * Remove path from Object. + * @param obj Object to change. + * @param path Path to remove. + */ + public static void removePathValue(Object obj, String path) { + Segments segments = tryResolvePath(obj, path); + if (segments == null) { + return; + } + + Object current = obj; + for (int i = 0; i < segments.size() - 1; i++) { + Object segment = segments.get(i); + current = resolveSegment(current, segment); + if (current == null) { + return; + } + } + + if (current != null) { + Object lastSegment = segments.last(); + if (lastSegment instanceof String) { + // lastSegment is a field name + if (current instanceof Map) { + ((Map) current).remove((String) lastSegment); + } else { + ((ObjectNode) current).remove((String) lastSegment); + } + } else { + // lastSegment is an index + ((ArrayNode) current).set((int) lastSegment, null); + } + } + } + + /** + * Apply an action to all properties in an Object. + * @param obj Object to map against. + * @param action Action to take. + */ + public static void forEachProperty(Object obj, BiConsumer action) { + if (obj instanceof Map) { + ((Map) obj).forEach(action); + } else if (obj instanceof ObjectNode) { + ObjectNode node = (ObjectNode) obj; + Iterator fields = node.fieldNames(); + + while (fields.hasNext()) { + String field = fields.next(); + action.accept(field, node.findValue(field)); + } + } + } + + /** + * Get all properties in an Object. + * @param obj Object to enumerate property names. + * @return enumeration of property names on the Object if it is not a value type. + */ + public static Collection getProperties(Object obj) { + if (obj == null) { + return new ArrayList<>(); + } else if (obj instanceof Map) { + return ((Map) obj).keySet(); + } else if (obj instanceof JsonNode) { + return IteratorUtils.toList(((JsonNode) obj).fieldNames()); + } else { + List fields = new ArrayList<>(); + for (Field field : obj.getClass().getDeclaredFields()) { + fields.add(field.getName()); + } + + return fields; + } + } + + /** + * Detects if property exists on Object. + * @param obj Object. + * @param name name of the property. + * @return true if found. + */ + public static boolean containsProperty(Object obj, String name) { + if (obj == null) { + return false; + } + + if (obj instanceof Map) { + return ((Map) obj).containsKey(name); + } + + if (obj instanceof JsonNode) { + return ((JsonNode) obj).findValue(name) != null; + } + + for (Field field : obj.getClass().getDeclaredFields()) { + if (StringUtils.equalsIgnoreCase(field.getName(), name)) { + return true; + } + } + return false; + } + + /** + * Clone an Object. + * @param Type to clone. + * @param obj The Object. + * @return The Object as Json. + */ + public static T clone(T obj) { + return (T) Serialization.getAs(obj, obj.getClass()); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The Object type. + * @param startObject Intial Object. + * @param overlayObject Overlay Object. + * @return merged Object. + */ + public static T merge(Object startObject, Object overlayObject) { + return (T) assign(startObject, overlayObject); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The Object type. + * @param startObject Intial Object. + * @param overlayObject Overlay Object. + * @param type Type of T + * @return merged Object. + */ + public static T merge(Object startObject, Object overlayObject, Class type) { + return (T) assign(startObject, overlayObject, type); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The target type. + * @param startObject overlay Object of any type. + * @param overlayObject overlay Object of any type. + * @return merged Object. + */ + public static T assign(T startObject, Object overlayObject) { + // FIXME this won't work for null startObject + return (T) assign(startObject, overlayObject, startObject.getClass()); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The Target type. + * @param startObject intial Object of any type. + * @param overlayObject overlay Object of any type. + * @param type type to output. + * @return merged Object. + */ + public static T assign(Object startObject, Object overlayObject, Class type) { + if (startObject != null && overlayObject != null) { + // make a deep clone JsonNode of the startObject + JsonNode merged = startObject instanceof JsonNode + ? (JsonNode) clone(startObject) + : Serialization.objectToTree(startObject); + + // get a JsonNode of the overlay Object + JsonNode overlay = overlayObject instanceof JsonNode + ? (JsonNode) overlayObject + : Serialization.objectToTree(overlayObject); + + merge(merged, overlay); + + return Serialization.treeToValue(merged, type); + } + + Object singleObject = startObject != null ? startObject : overlayObject; + if (singleObject != null) { + if (singleObject instanceof JsonNode) { + return Serialization.treeToValue((JsonNode) singleObject, type); + } + + return (T) singleObject; + } + + return null; +// TODO default object +// try { +// return singleObject.newInstance(); +// } catch (InstantiationException | IllegalAccessException e) { +// return null; +// } + } + + private static void merge(JsonNode startObject, JsonNode overlayObject) { + Set keySet = mergeKeys(startObject, overlayObject); + + for (String key : keySet) { + JsonNode targetValue = startObject.findValue(key); + JsonNode sourceValue = overlayObject.findValue(key); + + // skip empty overlay items + if (!isNull(sourceValue)) { + if (sourceValue instanceof ObjectNode) { + if (isNull(targetValue)) { + ((ObjectNode) startObject).set(key, clone(sourceValue)); + } else { + merge(targetValue, sourceValue); + } + } else { //if (targetValue instanceof NullNode) { + ((ObjectNode) startObject).set(key, clone(sourceValue)); + } + } + } + } + + private static boolean isNull(JsonNode node) { + return node == null || node instanceof NullNode; + } + + private static Set mergeKeys(JsonNode startObject, JsonNode overlayObject) { + Set keySet = new HashSet<>(); + Iterator> iter = startObject.fields(); + while (iter.hasNext()) { + Entry entry = iter.next(); + keySet.add(entry.getKey()); + } + + iter = overlayObject.fields(); + while (iter.hasNext()) { + Entry entry = iter.next(); + keySet.add(entry.getKey()); + } + + return keySet; + } + + /// + /// Convert a generic Object to a typed Object. + /// + /// type to convert to. + /// value to convert. + /// converted value. + /** + * Convert a generic Object to a typed Object. + * @param type to convert to. + * @param val value to convert. + * @param valueType Type of T + * @return converted value. + */ + public static T mapValueTo(Object val, Class valueType) { + if (val.getClass().equals(valueType)) { + return (T) val; + } + + if (val instanceof JsonNode) { + return Serialization.treeToValue((JsonNode) val, valueType); + } + + return Serialization.getAs(val, valueType); + + /* + if (val instanceof JValue) + { + return ((JValue)val).ToObject(); + } + + if (typeof(T) == typeof(Object)) + { + return (T)val; + } + + if (val is JArray) + { + return ((JArray)val).ToObject(); + } + + if (val is JObject) + { + return ((JObject)val).ToObject(); + } + + if (typeof(T) == typeof(JObject)) + { + return (T)(Object)JObject.FromObject(val); + } + + if (typeof(T) == typeof(JArray)) + { + return (T)(Object)JArray.FromObject(val); + } + + if (typeof(T) == typeof(JValue)) + { + return (T)(Object)JValue.FromObject(val); + } + + if (val is T) + { + return (T)val; + } + + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(val, _expressionCaseSettings)); + */ + } + + /** + * Given an root Object and property path, resolve to a constant if eval = true or a constant path otherwise. + * conversation[user.name][user.age] => ['conversation', 'joe', 32]. + * @param Type of T + * @param obj root Object. + * @param propertyPath property path to resolve. + * @return True if it was able to resolve all nested references. + */ + public static Segments tryResolvePath(Object obj, String propertyPath) { + return tryResolvePath(obj, propertyPath, false); + } + + /** + * Given an root Object and property path, resolve to a constant if eval = true or a constant path otherwise. + * conversation[user.name][user.age] => ['conversation', 'joe', 32]. + * @param Type of T + * @param obj root Object. + * @param propertyPath property path to resolve. + * @param eval True to evaluate resulting segments. + * @return True if it was able to resolve all nested references. + */ + public static Segments tryResolvePath(Object obj, String propertyPath, boolean eval) { + Segments soFar = new Segments(); + char first = propertyPath.length() > 0 ? propertyPath.charAt(0) : ' '; + if (first == '\'' || first == '"') { + if (!propertyPath.endsWith(String.valueOf(first))) { + return null; + } + + soFar.add(propertyPath.substring(1, propertyPath.length() - 1)); + } else if (isInt(propertyPath)) { + soFar.add(Integer.parseInt(propertyPath)); + } else { + int start = 0; + int i; + + // Scan path evaluating as we go + for (i = 0; i < propertyPath.length(); ++i) { + char ch = propertyPath.charAt(i); + if (ch == '.' || ch == '[') { + // emit + String segment = propertyPath.substring(start, i); + if (!StringUtils.isEmpty(segment)) { + soFar.add(segment); + } + start = i + 1; + } + + if (ch == '[') { + // Bracket expression + int nesting = 1; + while (++i < propertyPath.length()) { + ch = propertyPath.charAt(i); + if (ch == '[') { + ++nesting; + } else if (ch == ']') { + --nesting; + if (nesting == 0) { + break; + } + } + } + + if (nesting > 0) { + // Unbalanced brackets + return null; + } + + String expr = propertyPath.substring(start, i); + start = i + 1; + Segments indexer = tryResolvePath(obj, expr, true); + if (indexer == null || indexer.size() != 1) { + // Could not resolve bracket expression + return null; + } + + String result = mapValueTo(indexer.first(), String.class); + if (isInt(result)) { + soFar.add(Integer.parseInt(result)); + } else { + soFar.add(result); + } + } + } + + // emit + String segment = propertyPath.substring(start, i); + if (!StringUtils.isEmpty(segment)) { + soFar.add(segment); + } + start = i + 1; + + if (eval) { + Object result = resolveSegments(obj, soFar); + if (result == null) { + return null; + } + + soFar.clear(); + soFar.add(mapValueTo(result, Object.class)); + } + } + + return soFar; + } + + private static Object resolveSegment(Object current, Object segment) { + if (current != null) { + if (segment instanceof Integer) { + int index = (Integer) segment; + + if (current instanceof List) { + current = ((List) current).get(index); + } else { + current = Array.get(current, index); + } + } else { + current = getObjectProperty(current, (String) segment); + } + } + + return current; + } + + private static Object resolveSegments(Object current, Segments segments) { + Object result = current; + for (Object segment : segments) { + result = resolveSegment(result, segment); + if (result == null) { + return null; + } + } + + return result; + } + + /// + /// Get a property or array element from an Object. + /// + /// Object. + /// property or array segment to get relative to the Object. + /// the value or null if not found. + private static Object getObjectProperty(Object obj, String property) { + if (obj == null) { + return null; + } + + if (obj instanceof Map) { + Map dict = (Map) obj; + List> matches = dict.entrySet().stream() + .filter(key -> key.getKey().equalsIgnoreCase(property)) + .collect(Collectors.toList()); + + if (matches.size() > 0) { + return matches.get(0).getValue(); + } + + return null; + } + + if (obj instanceof JsonNode) { + JsonNode node = (JsonNode) obj; + Iterator fields = node.fieldNames(); + while (fields.hasNext()) { + String field = fields.next(); + if (field.equalsIgnoreCase(property)) { + return node.findValue(property); + } + } + return null; + } + + /* + //!!! not sure Java equiv + if (obj is JValue jval) + { + // in order to make things like "this.value.Length" work, when "this.value" is a String. + return getObjectProperty(jval.Value, property); + } + */ + + // reflection on Object + List matches = Arrays.stream(obj.getClass().getDeclaredFields()) + .filter(field -> field.getName().equalsIgnoreCase(property)) + .map(field -> { + try { + return field.get(obj); + } catch (IllegalAccessException e) { + return null; + } + }) + .collect(Collectors.toList()); + + if (matches.size() > 0) { + return matches.get(0); + } + return null; + } + + /// + /// Given an Object, set a property or array element on it with a value. + /// + /// Object to modify. + /// property or array segment to put the value in. + /// value to store. + /// if true, value will be normalized to JSON primitive Objects. + @SuppressWarnings("PMD.UnusedFormalParameter") + private static void setObjectSegment(Object obj, Object segment, Object value) { + setObjectSegment(obj, segment, value, true); + } + + @SuppressWarnings("PMD.EmptyCatchBlock") + private static void setObjectSegment(Object obj, Object segment, Object value, boolean json) { + Object normalizedValue = getNormalizedValue(value, json); + + // Json Array + if (segment instanceof Integer) { + ArrayNode jar = (ArrayNode) obj; + int index = (Integer) segment; + + if (index >= jar.size()) { + jar.add(index + 1 - jar.size()); + } + + jar.set(index, Serialization.objectToTree(normalizedValue)); + return; + } + + // Map + String property = (String) segment; + if (obj instanceof Map) { + Boolean wasSet = false; + Map dict = (Map) obj; + for (String key : dict.keySet()) { + if (key.equalsIgnoreCase(property)) { + wasSet = true; + dict.put(key, normalizedValue); + break; + } + } + if (!wasSet) { + dict.put(property, normalizedValue); + } + + return; + } + + // ObjectNode + if (obj instanceof ObjectNode) { + boolean wasSet = false; + ObjectNode node = (ObjectNode) obj; + Iterator fields = node.fieldNames(); + while (fields.hasNext()) { + String field = fields.next(); + if (field.equalsIgnoreCase(property)) { + wasSet = true; + node.set(property, Serialization.objectToTree(normalizedValue)); + break; + } + } + if (!wasSet) { + node.set(property, Serialization.objectToTree(normalizedValue)); + } + + return; + } + + // reflection + if (obj != null) { + for (Field f : obj.getClass().getDeclaredFields()) { + if (f.getName().equalsIgnoreCase(property)) { + try { + f.set(obj, normalizedValue); + } catch (IllegalAccessException ignore) { + } + } + } + } + } + + /// + /// Normalize value as json Objects. + /// + /// value to normalize. + /// normalize as json Objects. + /// normalized value. + private static Object getNormalizedValue(Object value, boolean json) { + Object val; + + if (json) { + //TODO revisit this (from dotnet) + if (value instanceof JsonNode) { + val = clone(value); + } else if (value == null) { + val = null; + } else { + val = clone(value); + } + } else { + val = value; + } + + return val; + } + + private static boolean isInt(String value) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java new file mode 100644 index 000000000..e2e7bf03f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.HashMap; + +/** + * Represents the persisted data across turns. + */ +public class PersistedState { + + /** + * The user state. + */ + private HashMap userState; + + /** + * The converation state. + */ + private HashMap conversationState; + + /** + * Constructs a PersistedState object. + */ + public PersistedState() { + userState = new HashMap(); + conversationState = new HashMap(); + } + + /** + * Initializes a new instance of the PersistedState class. + * + * @param keys The persisted keys. + * @param data The data containing the state values. + */ + @SuppressWarnings("unchecked") + public PersistedState(PersistedStateKeys keys, HashMap data) { + if (data.containsKey(keys.getUserState())) { + userState = (HashMap) data.get(keys.getUserState()); + } + if (data.containsKey(keys.getConversationState())) { + userState = (HashMap) data.get(keys.getConversationState()); + } + } + + /** + * @return userState Gets the user profile data. + */ + public HashMap getUserState() { + return this.userState; + } + + /** + * @param withUserState Sets user profile data. + */ + public void setUserState(HashMap withUserState) { + this.userState = withUserState; + } + + /** + * @return conversationState Gets the dialog state data. + */ + public HashMap getConversationState() { + return this.conversationState; + } + + /** + * @param withConversationState Sets the dialog state data. + */ + public void setConversationState(HashMap withConversationState) { + this.conversationState = withConversationState; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java new file mode 100644 index 000000000..17c10440a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java @@ -0,0 +1,48 @@ +// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. All rights reserved. + +package com.microsoft.bot.dialogs; + +/** + * These are the keys which are persisted. + */ +public class PersistedStateKeys { + /** + * The key for the user state. + */ + private String userState; + + /** + * The conversation state. + */ + private String conversationState; + + /** + * @return Gets the user state; + */ + public String getUserState() { + return this.userState; + } + + /** + * @param withUserState Sets the user state. + */ + public void setUserState(String withUserState) { + this.userState = withUserState; + } + + /** + * @return Gets the conversation state. + */ + public String getConversationState() { + return this.conversationState; + } + + /** + * @param withConversationState Sets the conversation state. + */ + public void setConversationState(String withConversationState) { + this.conversationState = withConversationState; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java new file mode 100644 index 000000000..5ad4a54bb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines paths for the available scopes. + */ +public final class ScopePath { + + private ScopePath() { + } + + /** + * User memory scope root path. + */ + public static final String USER = "user"; + + /** + * Conversation memory scope root path. + */ + public static final String CONVERSATION = "conversation"; + + /** + * Dialog memory scope root path. + */ + public static final String DIALOG = "dialog"; + + /** + * DialogClass memory scope root path. + */ + public static final String DIALOG_CLASS = "dialogclass"; + + /** + * DialogContext memory scope root path. + */ + public static final String DIALOG_CONTEXT = "dialogContext"; + + /** + * This memory scope root path. + */ + public static final String THIS = "this"; + + /** + * Class memory scope root path. + */ + public static final String CLASS = "class"; + + /** + * Settings memory scope root path. + */ + public static final String SETTINGS = "settings"; + + /** + * Turn memory scope root path. + */ + public static final String TURN = "turn"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java new file mode 100644 index 000000000..b4b69f417 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path passed to the active dialog. + */ +public final class ThisPath { + + private ThisPath() { + } + + /** + * The options that were passed to the active dialog via options argument of + * BeginDialog. + */ + public static final String OPTIONS = "this.options"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java new file mode 100644 index 000000000..17d053f1c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path for avaiable turns. + */ +public final class TurnPath { + private TurnPath() { } + + /// The result from the last dialog that was called. + public static final String LAST_RESULT = "turn.lastresult"; + + /// The current activity for the turn. + public static final String ACTIVITY = "turn.activity"; + + /// The recognized result for the current turn. + public static final String RECOGNIZED = "turn.recognized"; + + /// Path to the top intent. + public static final String TOP_INTENT = "turn.recognized.intent"; + + /// Path to the top score. + public static final String TOP_SCORE = "turn.recognized.score"; + + /// Original text. + public static final String TEXT = "turn.recognized.text"; + + /// Original utterance split into unrecognized strings. + public static final String UNRECOGNIZED_TEXT = "turn.unrecognizedText"; + + /// Entities that were recognized from text. + public static final String RECOGNIZED_ENTITIES = "turn.recognizedEntities"; + + /// If true an interruption has occured. + public static final String INTERRUPTED = "turn.interrupted"; + + /// The current dialog event (set during event processing). + public static final String DIALOG_EVENT = "turn.dialogEvent"; + + /// Used to track that we don't end up in infinite loop of RepeatDialogs(). + public static final String REPEATED_IDS = "turn.repeatedIds"; + + /// This is a bool which if set means that the turncontext.activity has been consumed by + // some component in the system. + public static final String ACTIVITY_PROCESSED = "turn.activityProcessed"; + + /** + * Utility function to get just the property name without the memory scope prefix. + * @param property memory scope property path. + * @return name of the property without the prefix. + */ + public static String getPropertyName(String property) { + return property.replace("turn.", ""); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java new file mode 100644 index 000000000..aedde17ba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.StringUtils; + +/** + * Dialog optimized for prompting a user with a series of questions. Waterfalls + * accept a stack of functions which will be executed in sequence. Each + * waterfall step can ask a question of the user and the user's response will be + * passed as an argument to the next waterfall step. + */ +public class WaterfallDialog extends Dialog { + + private final String persistedOptions = "options"; + private final String stepIndex = "stepIndex"; + private final String persistedValues = "values"; + private final String persistedInstanceId = "instanceId"; + + private final List steps; + + /** + * Initializes a new instance of the {@link waterfallDialog} class. + * + * @param dialogId The dialog ID. + * @param actions Optional actions to be defined by the caller. + */ + public WaterfallDialog(String dialogId, Collection actions) { + super(dialogId); + steps = actions != null ? new ArrayList(actions) : new ArrayList(); + } + + /** + * Gets a unique String which represents the version of this dialog. If the + * version changes between turns the dialog system will emit a DialogChanged + * event. + * + * @return Version will change when steps count changes (because dialog has no + * way of evaluating the content of the steps. + */ + public String getVersion() { + return String.format("%s:%d", getId(), steps.size()); + } + + /** + * Adds a new step to the waterfall. + * + * @param step Step to add. + * @return Waterfall dialog for fluent calls to `AddStep()`. + */ + public WaterfallDialog addStep(WaterfallStep step) { + if (step == null) { + throw new IllegalArgumentException("step cannot be null"); + } + steps.add(step); + return this; + } + + /** + * Called when the waterfall dialog is started and pushed onto the dialog stack. + * + * @param dc The + * @param options Optional, initial information to pass to the dialog. + * @return A CompletableFuture representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Initialize waterfall state + Map state = dc.getActiveDialog().getState(); + String instanceId = UUID.randomUUID().toString(); + state.put(persistedOptions, options); + state.put(persistedValues, new HashMap()); + state.put(persistedInstanceId, instanceId); + + Map properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("InstanceId", instanceId); + + getTelemetryClient().trackEvent("WaterfallStart", properties); + getTelemetryClient().trackDialogView(getId(), null, null); + + // Run first step + return runStep(dc, 0, DialogReason.BEGIN_CALLED, null); + } + + /** + * Called when the waterfall dialog is _continued_, where it is the active + * dialog and the user replies with a new activity. + * + * @param dc The + * @return A CompletableFuture representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Don't do anything for non-message activities. + if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) { + return CompletableFuture.completedFuture(END_OF_TURN); + } + + // Run next step with the message text as the result. + return resumeDialog(dc, DialogReason.CONTINUE_CALLED); + } + + /** + * Called when a child waterfall dialog completed its turn, returning control to + * this dialog. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Increment step index and run step + Map state = dc.getActiveDialog().getState(); + int index = 0; + if (state.containsKey(stepIndex)) { + index = (int) state.get(stepIndex); + } + + return runStep(dc, index + 1, reason, result); + } + + /** + * Called when the dialog is ending. + * + * @param turnContext Context for the current turn of the conversation. + * @param instance The instance of the current dialog. + * @param reason The reason the dialog is ending. + * + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + if (reason == DialogReason.CANCEL_CALLED) { + HashMap state = new HashMap((Map) instance.getState()); + + // Create step context + int index = (int) state.get(stepIndex); + String stepName = waterfallStepName(index); + String instanceId = (String) state.get(persistedInstanceId); + + HashMap properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("StepName", stepName); + properties.put("InstanceId", instanceId); + + getTelemetryClient().trackEvent("WaterfallCancel", properties); + } else if (reason == DialogReason.END_CALLED) { + HashMap state = new HashMap((Map) instance.getState()); + String instanceId = (String) state.get(persistedInstanceId); + + HashMap properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("InstanceId", instanceId); + getTelemetryClient().trackEvent("WaterfallComplete", properties); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Called when an individual waterfall step is being executed. + * + * @param stepContext Context for the waterfall step to execute. + * + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onStep(WaterfallStepContext stepContext) { + String stepName = waterfallStepName(stepContext.getIndex()); + String instanceId = (String) stepContext.getActiveDialog().getState().get(persistedInstanceId); + + HashMap properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("StepName", stepName); + properties.put("InstanceId", instanceId); + + getTelemetryClient().trackEvent("WaterfallStep", properties); + + return steps.get(stepContext.getIndex()).waterfallStep(stepContext); + } + + /** + * Excutes a step of the waterfall dialog. + * + * @param dc The {@link DialogContext} for the current turn of conversation. + * @param index The index of the current waterfall step to execute. + * @param reason The reason the waterfall step is being executed. + * @param result Result returned by a dialog called in the previous waterfall step. + * + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture runStep(DialogContext dc, int index, + DialogReason reason, Object result) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (index < steps.size()) { + // Update persisted step index + Map state = (Map) dc.getActiveDialog().getState(); + + state.put(stepIndex, index); + + // Create step context + Object options = state.get(persistedOptions); + Map values = (Map) state.get(persistedValues); + WaterfallStepContext stepContext = + new WaterfallStepContext(this, dc, options, values, index, reason, result); + + // Execute step + return onStep(stepContext); + } + + // End of waterfall so just return any result to parent + return dc.endDialog(result); + } + + private String waterfallStepName(int index) { + // Log Waterfall Step event. Each event has a distinct name to hook up + // to the Application Insights funnel. + String stepName = steps.get(index).getClass().getSimpleName(); + + // Default stepname for lambdas + if (StringUtils.isAllBlank(stepName) || stepName.contains("<")) { + stepName = String.format("Step%0of%1", index + 1, steps.size()); + } + + return stepName; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java new file mode 100644 index 000000000..37266c05f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.concurrent.CompletableFuture; + +/** + * A interface definition of a Waterfall step. This is implemented by + * application code. + */ +public interface WaterfallStep { + /** + * A interface definition of a Waterfall step. This is implemented by + * application code. + * + * @param stepContext The WaterfallStepContext for this waterfall dialog. + * + * @return A {@link CompletableFuture} of {@link DialogTurnResult} representing + * the asynchronous operation. + */ + CompletableFuture waterfallStep(WaterfallStepContext stepContext); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java new file mode 100644 index 000000000..91794a485 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.Async; + +/** + * Provides context for a step in a {@link waterfallDialog} . + * + * The {@link DialogContext#context} property contains the {@link TurnContext} + * for the current turn. + */ +public class WaterfallStepContext extends DialogContext { + + private final WaterfallDialog parentWaterfall; + private Boolean nextCalled; + + /** + * Initializes a new instance of the {@link WaterfallStepContext} class. + * + * @param parentWaterfall The parent of the waterfall dialog. + * @param dc The dialog's context. + * @param options Any options to call the waterfall dialog with. + * @param values A dictionary of values which will be persisted across all + * waterfall steps. + * @param index The index of the current waterfall to execute. + * @param reason The reason the waterfall step is being executed. + * @param result Results returned by a dialog called in the previous waterfall + * step. + */ + public WaterfallStepContext( + WaterfallDialog parentWaterfall, + DialogContext dc, + Object options, + Map values, + int index, + DialogReason reason, + Object result) { + super(dc.getDialogs(), dc, new DialogState(dc.getStack())); + this.parentWaterfall = parentWaterfall; + this.nextCalled = false; + this.setParent(dc.getParent()); + this.index = index; + this.options = options; + this.reason = reason; + this.result = result; + this.values = values; + } + + private int index; + + private Object options; + + private DialogReason reason; + + private Object result; + + private Map values; + + /** + * Gets the index of the current waterfall step being executed. + * @return returns the index value; + */ + public int getIndex() { + return this.index; + } + + /** + * Gets any options the waterfall dialog was called with. + * @return The options. + */ + public Object getOptions() { + return this.options; + } + + /** + * Gets the reason the waterfall step is being executed. + * @return The DialogReason + */ + public DialogReason getReason() { + return this.reason; + } + + /** + * Gets the result from the previous waterfall step. + * + * The result is often the return value of a child dialog that was started in the previous step + * of the waterfall. + * @return the Result value. + */ + public Object getResult() { + return this.result; + } + + /** + * Gets a dictionary of values which will be persisted across all waterfall actions. + * @return The Dictionary of values. + */ + public Map getValues() { + return this.values; + } + + /** + * Skips to the next step of the waterfall. + * + * @param result Optional, result to pass to the next step of the current waterfall + * dialog. + * @return A CompletableFuture that represents the work queued to execute. + * + * In the next step of the waterfall, the {@link result} property of the waterfall step context + * will contain the value of the . + */ + + public CompletableFuture next(Object result) { + // Ensure next hasn't been called + if (nextCalled) { + return Async.completeExceptionally(new IllegalStateException( + String.format("WaterfallStepContext.next(): method already called for dialog and step '%0 %1", + parentWaterfall.getId(), index) + )); + } + + // Trigger next step + nextCalled = true; + return parentWaterfall.resumeDialog(this, DialogReason.NEXT_CALLED, result); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java new file mode 100644 index 000000000..647bfd4bf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Channels; +import org.apache.commons.lang3.StringUtils; + +/** + * Methods for determining channel specific functionality. + */ +public final class Channel { + private Channel() { } + + /** + * Determine if a number of Suggested Actions are supported by a Channel. + * + * @param channelId The Channel to check the if Suggested Actions are supported in. + * @return True if the Channel supports the buttonCnt total Suggested Actions, False if + * the Channel does not support that number of Suggested Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsSuggestedActions(String channelId) { + return supportsSuggestedActions(channelId, 100); + } + + /** + * Determine if a number of Suggested Actions are supported by a Channel. + * + * @param channelId The Channel to check the if Suggested Actions are supported in. + * @param buttonCnt The number of Suggested Actions to check for the Channel. + * @return True if the Channel supports the buttonCnt total Suggested Actions, False if + * the Channel does not support that number of Suggested Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsSuggestedActions(String channelId, int buttonCnt) { + switch (channelId) { + // https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies + case Channels.FACEBOOK: + case Channels.SKYPE: + return buttonCnt <= 10; + + // https://developers.line.biz/en/reference/messaging-api/#items-object + case Channels.LINE: + return buttonCnt <= 13; + + // https://dev.kik.com/#/docs/messaging#text-response-object + case Channels.KIK: + return buttonCnt <= 20; + + case Channels.TELEGRAM: + case Channels.EMULATOR: + case Channels.DIRECTLINE: + case Channels.DIRECTLINESPEECH: + case Channels.WEBCHAT: + return buttonCnt <= 100; + + default: + return false; + } + } + + /** + * Determine if a number of Card Actions are supported by a Channel. + * + * @param channelId The Channel to check if the Card Actions are supported in. + * @return True if the Channel supports the buttonCnt total Card Actions, False if the + * Channel does not support that number of Card Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsCardActions(String channelId) { + return supportsCardActions(channelId, 100); + } + + /** + * Determine if a number of Card Actions are supported by a Channel. + * + * @param channelId The Channel to check if the Card Actions are supported in. + * @param buttonCnt The number of Card Actions to check for the Channel. + * @return True if the Channel supports the buttonCnt total Card Actions, False if the + * Channel does not support that number of Card Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsCardActions(String channelId, int buttonCnt) { + switch (channelId) { + case Channels.FACEBOOK: + case Channels.SKYPE: + case Channels.MSTEAMS: + return buttonCnt <= 3; + + case Channels.LINE: + return buttonCnt <= 99; + + case Channels.SLACK: + case Channels.EMULATOR: + case Channels.DIRECTLINE: + case Channels.DIRECTLINESPEECH: + case Channels.WEBCHAT: + case Channels.CORTANA: + return buttonCnt <= 100; + + default: + return false; + } + } + + /** + * Determine if a Channel has a Message Feed. + * @param channelId The Channel to check for Message Feed. + * @return True if the Channel has a Message Feed, False if it does not. + */ + public static boolean hasMessageFeed(String channelId) { + switch (channelId) { + case Channels.CORTANA: + return false; + + default: + return true; + } + } + + /** + * Maximum length allowed for Action Titles. + * + * @param channelId The Channel to determine Maximum Action Title Length. + * @return The total number of characters allowed for an Action Title on a specific Channel. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static int maxActionTitleLength(String channelId) { + return 20; + } + + /** + * Get the Channel Id from the current Activity on the Turn Context. + * + * @param turnContext The Turn Context to retrieve the Activity's Channel Id from. + * @return The Channel Id from the Turn Context's Activity. + */ + public static String getChannelId(TurnContext turnContext) { + return StringUtils.isEmpty(turnContext.getActivity().getChannelId()) + ? null + : turnContext.getActivity().getChannelId(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java new file mode 100644 index 000000000..66321427d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.schema.CardAction; +import java.util.List; + +/** + * Represents a choice for a choice prompt. + */ +public class Choice { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "action") + private CardAction action; + + @JsonProperty(value = "synonyms") + private List synonyms; + + /** + * Creates a Choice. + */ + public Choice() { + this(null); + } + + /** + * Creates a Choice. + * @param withValue The value. + */ + public Choice(String withValue) { + value = withValue; + } + + /** + * Gets the value to return when selected. + * @return The value. + */ + public String getValue() { + return value; + } + + /** + * Sets the value to return when selected. + * @param withValue The value to return. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the action to use when rendering the choice as a suggested action or hero card. + * @return The action to use. + */ + public CardAction getAction() { + return action; + } + + /** + * Sets the action to use when rendering the choice as a suggested action or hero card. + * @param withAction The action to use. + */ + public void setAction(CardAction withAction) { + action = withAction; + } + + /** + * Gets the list of synonyms to recognize in addition to the value. This is optional. + * @return The list of synonyms. + */ + public List getSynonyms() { + return synonyms; + } + + /** + * Sets the list of synonyms to recognize in addition to the value. This is optional. + * @param withSynonyms The list of synonyms. + */ + public void setSynonyms(List withSynonyms) { + synonyms = withSynonyms; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java new file mode 100644 index 000000000..7e484021e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java @@ -0,0 +1,399 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.InputHints; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +/** + * Assists with formatting a message activity that contains a list of choices. + */ +public final class ChoiceFactory { + private ChoiceFactory() { + } + + /** + * Creates an Activity that includes a list of choices formatted based on the + * capabilities of a given channel. + * + * @param channelId A channel ID. The Connector.Channels class contains known + * channel IDs. + * @param list The list of choices to include. + * @param text The text of the message to send. Can be null. + * @return The created Activity + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static Activity forChannel(String channelId, List list, String text) { + return forChannel(channelId, list, text, null, null); + } + + /** + * Creates an Activity that includes a list of choices formatted based on the + * capabilities of a given channel. + * + * @param channelId A channel ID. The Connector.Channels class contains known + * channel IDs. + * @param list The list of choices to include. + * @param text The text of the message to send. Can be null. + * @param speak The text to be spoken by your bot on a speech-enabled + * channel. Can be null. + * @param options The formatting options to use when rendering as a list. If + * null, the default options are used. + * @return The created Activity + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static Activity forChannel(String channelId, List list, String text, String speak, + ChoiceFactoryOptions options) { + if (list == null) { + list = new ArrayList<>(); + } + + // Find maximum title length + int maxTitleLength = 0; + for (Choice choice : list) { + int l = choice.getAction() != null && !StringUtils.isEmpty(choice.getAction().getTitle()) + ? choice.getAction().getTitle().length() + : choice.getValue().length(); + + if (l > maxTitleLength) { + maxTitleLength = l; + } + } + + // Determine list style + boolean supportsSuggestedActions = Channel.supportsSuggestedActions(channelId, list.size()); + boolean supportsCardActions = Channel.supportsCardActions(channelId, list.size()); + int maxActionTitleLength = Channel.maxActionTitleLength(channelId); + boolean longTitles = maxTitleLength > maxActionTitleLength; + + if (!longTitles && !supportsSuggestedActions && supportsCardActions) { + // SuggestedActions is the preferred approach, but for channels that don't + // support them (e.g. Teams, Cortana) we should use a HeroCard with CardActions + return heroCard(list, text, speak); + } + + if (!longTitles && supportsSuggestedActions) { + // We always prefer showing choices using suggested actions. If the titles are + // too long, however, + // we'll have to show them as a text list. + return suggestedAction(list, text, speak); + } + + if (!longTitles && list.size() <= 3) { + // If the titles are short and there are 3 or less choices we'll use an inline + // list. + return inline(list, text, speak, options); + } + + // Show a numbered list. + return list(list, text, speak, options); + } + + /** + * Creates an Activity that includes a list of choices formatted as an inline + * list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. Can be null. + * @return The created Activity. + */ + public static Activity inline(List choices, String text) { + return inline(choices, text, null, null); + } + + /** + * Creates an Activity that includes a list of choices formatted as an inline + * list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. Can be null. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * Cab be null. + * @param options The formatting options to use when rendering as a list. Can be + * null. + * @return The created Activity. + */ + public static Activity inline(List choices, String text, String speak, ChoiceFactoryOptions options) { + if (choices == null) { + choices = new ArrayList<>(); + } + + // clone options with defaults applied if needed. + ChoiceFactoryOptions opt = new ChoiceFactoryOptions(options); + + // Format list of choices + String connector = ""; + StringBuilder txtBuilder; + if (StringUtils.isNotBlank(text)) { + txtBuilder = new StringBuilder(text).append(' '); + } else { + txtBuilder = new StringBuilder().append(' '); + } + + for (int index = 0; index < choices.size(); index++) { + Choice choice = choices.get(index); + String title = choice.getAction() != null && choice.getAction().getTitle() != null + ? choice.getAction().getTitle() + : choice.getValue(); + + txtBuilder.append(connector); + if (opt.getIncludeNumbers()) { + txtBuilder.append('(').append(index + 1).append(") "); + } + + txtBuilder.append(title); + if (index == choices.size() - 2) { + connector = index == 0 ? opt.getInlineOr() : opt.getInlineOrMore(); + } else { + connector = opt.getInlineSeparator(); + } + } + + // Return activity with choices as an inline list. + return MessageFactory.text(txtBuilder.toString(), speak, InputHints.EXPECTING_INPUT); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of strings to include as Choices. + * @return The created Activity. + */ + public static Activity listFromStrings(List choices) { + return listFromStrings(choices, null, null, null); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of strings to include as Choices. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @param options The formatting options to use when rendering as a list. + * @return The created Activity. + */ + public static Activity listFromStrings(List choices, String text, String speak, + ChoiceFactoryOptions options) { + return list(toChoices(choices), text, speak, options); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of choices to include. + * @return The created Activity. + */ + public static Activity list(List choices) { + return list(choices, null, null, null); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @return The created Activity. + */ + public static Activity list(List choices, String text) { + return list(choices, text, null, null); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @param options The formatting options to use when rendering as a list. + * @return The created Activity. + */ + public static Activity list(List choices, String text, String speak, ChoiceFactoryOptions options) { + if (choices == null) { + choices = new ArrayList<>(); + } + + // clone options with defaults applied if needed. + ChoiceFactoryOptions opt = new ChoiceFactoryOptions(options); + + boolean includeNumbers = opt.getIncludeNumbers(); + + // Format list of choices + String connector = ""; + StringBuilder txtBuilder = text == null ? new StringBuilder() : new StringBuilder(text).append("\n\n "); + + for (int index = 0; index < choices.size(); index++) { + Choice choice = choices.get(index); + + String title = choice.getAction() != null && !StringUtils.isEmpty(choice.getAction().getTitle()) + ? choice.getAction().getTitle() + : choice.getValue(); + + txtBuilder.append(connector); + if (includeNumbers) { + txtBuilder.append(index + 1).append(". "); + } else { + txtBuilder.append("- "); + } + + txtBuilder.append(title); + connector = "\n "; + } + + // Return activity with choices as a numbered list. + return MessageFactory.text(txtBuilder.toString(), speak, InputHints.EXPECTING_INPUT); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of strings to include as actions. + * @return The created Activity. + */ + public static Activity suggestedActionFromStrings(List choices) { + return suggestedActionFromStrings(choices, null, null); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of strings to include as actions. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @return The created Activity. + */ + public static Activity suggestedActionFromStrings(List choices, String text, String speak) { + return suggestedAction(toChoices(choices), text, speak); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of choices to include. + * @return The created Activity. + */ + public static Activity suggestedAction(List choices) { + return suggestedAction(choices, null, null); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @return The created Activity. + */ + public static Activity suggestedAction(List choices, String text) { + return suggestedAction(choices, text, null); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @return The created Activity. + */ + public static Activity suggestedAction(List choices, String text, String speak) { + // Return activity with choices as suggested actions + return MessageFactory.suggestedCardActions(extractActions(choices), text, speak, InputHints.EXPECTING_INPUT); + } + + /** + * Creates an Activity with a HeroCard based on a list of Choices. + * + * @param choices The list of choices to include. + * @return The created Activity. + */ + public static Activity heroCard(List choices) { + return heroCard(choices, null, null); + } + + /** + * Creates an Activity with a HeroCard based on a list of Choices. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @return The created Activity. + */ + public static Activity heroCard(List choices, String text) { + return heroCard(choices, text, null); + } + + /** + * Creates an Activity with a HeroCard based on a list of Choices. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @return The created Activity. + */ + public static Activity heroCard(List choices, String text, String speak) { + HeroCard card = new HeroCard() { + { + setText(text); + setButtons(extractActions(choices)); + } + }; + + List attachments = new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add(card.toAttachment()); + } + }; + + // Return activity with choices as HeroCard with buttons + return MessageFactory.attachment(attachments, null, speak, InputHints.EXPECTING_INPUT); + } + + /** + * Returns a list of strings as a list of Choices. + * + * @param choices The list of strings to convert. + * @return A List of Choices. + */ + public static List toChoices(List choices) { + return choices == null ? new ArrayList<>() : choices.stream().map(Choice::new).collect(Collectors.toList()); + } + + private static List extractActions(List choices) { + if (choices == null) { + choices = new ArrayList<>(); + } + + // Map choices to actions + return choices.stream().map(choice -> { + if (choice.getAction() != null) { + return choice.getAction(); + } + + return new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue(choice.getValue()); + setTitle(choice.getValue()); + } + }; + }).collect(Collectors.toList()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java new file mode 100644 index 000000000..62a20dbda --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + +/** + * Contains formatting options for presenting a list of choices. + */ +public class ChoiceFactoryOptions { + public static final String DEFAULT_INLINE_SEPERATOR = ", "; + public static final String DEFAULT_INLINE_OR = " or "; + public static final String DEFAULT_INLINE_OR_MORE = ", or "; + public static final boolean DEFAULT_INCLUDE_NUMBERS = true; + + /** + * Creates default options. + */ + public ChoiceFactoryOptions() { + this(DEFAULT_INLINE_SEPERATOR, DEFAULT_INLINE_OR, DEFAULT_INLINE_OR_MORE); + } + + /** + * Clones another options object, and applies defaults if needed. + * + * @param options The options object to clone. + */ + public ChoiceFactoryOptions(ChoiceFactoryOptions options) { + this(); + + if (options != null) { + if (!StringUtils.isEmpty(options.getInlineSeparator())) { + setInlineSeparator(options.getInlineSeparator()); + } + + if (!StringUtils.isEmpty(options.getInlineOr())) { + setInlineOr(options.getInlineOr()); + } + + if (!StringUtils.isEmpty(options.getInlineOrMore())) { + setInlineOrMore(options.getInlineOrMore()); + } + + setIncludeNumbers(options.getIncludeNumbers()); + } + } + + /** + * Creates options with the specified formatting values. + * @param withInlineSeparator The inline seperator value. + * @param withInlineOr The inline or value. + * @param withInlineOrMore The inline or more value. + */ + public ChoiceFactoryOptions( + String withInlineSeparator, + String withInlineOr, + String withInlineOrMore + ) { + this(withInlineSeparator, withInlineOr, withInlineOrMore, DEFAULT_INCLUDE_NUMBERS); + } + + /** + * Initializes a new instance of the class. + * Refer to the code in teh ConfirmPrompt for an example of usage. + * @param withInlineSeparator The inline seperator value. + * @param withInlineOr The inline or value. + * @param withInlineOrMore The inline or more value. + * @param withIncludeNumbers Flag indicating whether to include numbers as a choice. + */ + public ChoiceFactoryOptions( + String withInlineSeparator, + String withInlineOr, + String withInlineOrMore, + boolean withIncludeNumbers + ) { + inlineSeparator = withInlineSeparator; + inlineOr = withInlineOr; + inlineOrMore = withInlineOrMore; + includeNumbers = withIncludeNumbers; + } + + @JsonProperty(value = "inlineSeparator") + private String inlineSeparator; + + @JsonProperty(value = "inlineOr") + private String inlineOr; + + @JsonProperty(value = "inlineOrMore") + private String inlineOrMore; + + @JsonProperty(value = "includeNumbers") + private Boolean includeNumbers; + + /** + * Gets the character used to separate individual choices when there are more than 2 choices. + * The default value is `", "`. This is optional. + * + * @return The seperator. + */ + public String getInlineSeparator() { + return inlineSeparator; + } + + /** + * Sets the character used to separate individual choices when there are more than 2 choices. + * @param withSeperator The seperator. + */ + public void setInlineSeparator(String withSeperator) { + inlineSeparator = withSeperator; + } + + /** + * Gets the separator inserted between the choices when their are only 2 choices. The default + * value is `" or "`. This is optional. + * + * @return The separator inserted between the choices when their are only 2 choices. + */ + public String getInlineOr() { + return inlineOr; + } + + /** + * Sets the separator inserted between the choices when their are only 2 choices. + * + * @param withInlineOr The separator inserted between the choices when their are only 2 choices. + */ + public void setInlineOr(String withInlineOr) { + this.inlineOr = withInlineOr; + } + + /** + * Gets the separator inserted between the last 2 choices when their are more than 2 choices. + * The default value is `", or "`. This is optional. + * + * @return The separator inserted between the last 2 choices when their are more than 2 choices. + */ + public String getInlineOrMore() { + return inlineOrMore; + } + + /** + * Sets the separator inserted between the last 2 choices when their are more than 2 choices. + * + * @param withInlineOrMore The separator inserted between the last 2 choices when their + * are more than 2 choices. + */ + public void setInlineOrMore(String withInlineOrMore) { + this.inlineOrMore = withInlineOrMore; + } + + /** + * Gets a value indicating whether an inline and list style choices will be prefixed + * with the index of the choice; as in "1. choice". If false, the list style will use a + * bulleted list instead. The default value is true. + * + * @return If false, the list style will use a bulleted list. + */ + public Boolean getIncludeNumbers() { + return includeNumbers; + } + + /** + * Sets the value indicating whether an inline and list style choices will be prefixed + * with the index of the choice. + * + * @param withIncludeNumbers If false, the list style will use a bulleted list instead. + */ + public void setIncludeNumbers(Boolean withIncludeNumbers) { + this.includeNumbers = withIncludeNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java new file mode 100644 index 000000000..8ae3e5d0a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.recognizers.text.IModel; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import com.microsoft.recognizers.text.number.NumberRecognizer; + +/** + * Contains methods for matching user input against a list of choices. + */ +public final class ChoiceRecognizers { + private ChoiceRecognizers() { } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoicesFromStrings(String utterance, List choices) { + return recognizeChoicesFromStrings(utterance, choices, null); + } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoicesFromStrings( + String utterance, + List choices, + FindChoicesOptions options + ) { + return recognizeChoices(utterance, choices.stream().map(Choice::new) + .collect(Collectors.toList()), options); + } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoices(String utterance, List choices) { + return recognizeChoices(utterance, choices, null); + } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoices( + String utterance, + List choices, + FindChoicesOptions options + ) { + // Try finding choices by text search first + // - We only want to use a single strategy for returning results to avoid issues where utterances + // like the "the third one" or "the red one" or "the first division book" would miss-recognize as + // a numerical index or ordinal as well. + String locale = options != null ? options.getLocale() : Locale.ENGLISH.getDisplayName(); + List> matched = Find.findChoices(utterance, choices, options); + if (matched.size() == 0) { + List> matches = new ArrayList<>(); + if (options == null || options.isRecognizeOrdinals()) { + // Next try finding by ordinal + matches = recognizeNumbers(utterance, new NumberRecognizer(locale).getOrdinalModel(locale, true)); + for (ModelResult match : matches) { + matchChoiceByIndex(choices, matched, match); + } + } + + if (matches.size() == 0 && (options == null || options.isRecognizeNumbers())) { + // Then try by numerical index + matches = recognizeNumbers(utterance, new NumberRecognizer(locale).getNumberModel(locale, true)); + for (ModelResult match : matches) { + matchChoiceByIndex(choices, matched, match); + } + } + + // Sort any found matches by their position within the utterance. + // - The results from findChoices() are already properly sorted so we just need this + // for ordinal & numerical lookups. + matched.sort(Comparator.comparingInt(ModelResult::getStart)); + } + + return matched; + } + + private static void matchChoiceByIndex( + List list, + List> matched, + ModelResult match + ) { + try { + // converts Resolution Values containing "end" (e.g. utterance "last") in numeric values. + String value = match.getResolution().getValue().replace("end", Integer.toString(list.size())); + int index = Integer.parseInt(value) - 1; + if (index >= 0 && index < list.size()) { + Choice choice = list.get(index); + matched.add(new ModelResult() {{ + setStart(match.getStart()); + setEnd(match.getEnd()); + setTypeName("choice"); + setText(match.getText()); + setResolution(new FoundChoice() {{ + setValue(choice.getValue()); + setIndex(index); + setScore(1.0f); + }}); + }}); + } + } catch (Throwable ignored) { + // noop here, as in dotnet/node repos + } + } + + private static List> recognizeNumbers(String utterance, IModel model) { + List result = model.parse(utterance == null ? "" : utterance); + return result.stream().map(r -> + new ModelResult() {{ + setStart(r.start); + setEnd(r.end - 1); // bug in 1.0-SNAPSHOT, should not have to decrement + setText(r.text); + setResolution(new FoundChoice() {{ + setValue(r.resolution.get("value").toString()); + }}); + }}).collect(Collectors.toList()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java new file mode 100644 index 000000000..c638003f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +/** + * Contains methods for matching user input against a list of choices. + */ +public final class Find { + private Find() { } + + /** + * Matches user input against a list of strings. + * @param utterance The input. + * @param choices The list of choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoicesFromStrings( + String utterance, + List choices + ) { + return findChoicesFromStrings(utterance, choices, null); + } + + /** + * Matches user input against a list of strings. + * @param utterance The input. + * @param choices The list of choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoicesFromStrings( + String utterance, + List choices, + FindChoicesOptions options + ) { + if (choices == null) { + throw new IllegalArgumentException("choices argument is missing"); + } + + return findChoices(utterance, choices.stream().map(Choice::new) + .collect(Collectors.toList()), options); + } + + /** + * Matches user input against a list of Choices. + * @param utterance The input. + * @param choices The list of choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoices(String utterance, List choices) { + return findChoices(utterance, choices, null); + } + + /** + * Matches user input against a list of Choices. + * @param utterance The input. + * @param choices The list of choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoices( + String utterance, + List choices, + FindChoicesOptions options + ) { + if (choices == null) { + throw new IllegalArgumentException("choices argument is missing"); + } + + FindChoicesOptions opt = options != null ? options : new FindChoicesOptions(); + + // Build up full list of synonyms to search over. + // - Each entry in the list contains the index of the choice it belongs to which will later be + // used to map the search results back to their choice. + List synonyms = new ArrayList<>(); + + for (int index = 0; index < choices.size(); index++) { + Choice choice = choices.get(index); + + if (!opt.isNoValue()) { + synonyms.add(new SortedValue(choice.getValue(), index)); + } + + if (choice.getAction() != null && choice.getAction().getTitle() != null && !opt.isNoAction()) { + synonyms.add(new SortedValue(choice.getAction().getTitle(), index)); + } + + if (choice.getSynonyms() != null) { + for (String synonym : choice.getSynonyms()) { + synonyms.add(new SortedValue(synonym, index)); + } + } + } + + // Find synonyms in utterance and map back to their choices + return findValues(utterance, synonyms, options).stream().map(v -> { + Choice choice = choices.get(v.getResolution().getIndex()); + + return new ModelResult() {{ + setStart(v.getStart()); + setEnd(v.getEnd()); + setTypeName("choice"); + setText(v.getText()); + setResolution(new FoundChoice() {{ + setValue(choice.getValue()); + setIndex(v.getResolution().getIndex()); + setScore(v.getResolution().getScore()); + setSynonym(v.getResolution().getValue()); + }}); + }}; + }).collect(Collectors.toList()); + } + + /** + * This method is internal and should not be used. + * @param utterance The input. + * @param values The values. + * @return A list of found values. + */ + static List> findValues(String utterance, List values) { + return findValues(utterance, values, null); + } + + /** + * This method is internal and should not be used. + * @param utterance The input. + * @param values The values. + * @param options The options for the search. + * @return A list of found values. + */ + static List> findValues( + String utterance, + List values, + FindValuesOptions options + ) { + // Sort values in descending order by length so that the longest value is searched over first. + List list = new ArrayList<>(values); + list.sort((a, b) -> b.getValue().length() - a.getValue().length()); + + // Search for each value within the utterance. + List> matches = new ArrayList<>(); + FindValuesOptions opt = options != null ? options : new FindValuesOptions(); + TokenizerFunction tokenizer = opt.getTokenizer() != null ? opt.getTokenizer() : new Tokenizer(); + List tokens = tokenizer.tokenize(utterance, opt.getLocale()); + int maxDistance = opt.getMaxTokenDistance(); + + for (SortedValue entry : list) { + // Find all matches for a value + // - To match "last one" in "the last time I chose the last one" we need + // to re-search the String starting from the end of the previous match. + // - The start & end position returned for the match are token positions. + int startPos = 0; + List searchedTokens = tokenizer.tokenize(entry.getValue().trim(), opt.getLocale()); + while (startPos < tokens.size()) { + ModelResult match = matchValue( + tokens, + maxDistance, + opt, + entry.getIndex(), + entry.getValue(), + searchedTokens, + startPos + ); + if (match != null) { + startPos = match.getEnd() + 1; + matches.add(match); + } else { + break; + } + } + } + + // Sort matches by score descending + matches.sort((a, b) -> Float.compare(b.getResolution().getScore(), a.getResolution().getScore())); + + // Filter out duplicate matching indexes and overlapping characters. + // - The start & end positions are token positions and need to be translated to + // character positions before returning. We also need to populate the "text" + // field as well. + List> results = new ArrayList<>(); + Set foundIndexes = new HashSet<>(); + Set usedTokens = new HashSet<>(); + + for (ModelResult match : matches) { + // Apply filters + boolean add = !foundIndexes.contains(match.getResolution().getIndex()); + for (int i = match.getStart(); i <= match.getEnd(); i++) { + if (usedTokens.contains(i)) { + add = false; + break; + } + } + + // Add to results + if (add) { + // Update filter info + foundIndexes.add(match.getResolution().getIndex()); + + for (int i = match.getStart(); i <= match.getEnd(); i++) { + usedTokens.add(i); + } + + // Translate start & end and populate text field + match.setStart(tokens.get(match.getStart()).getStart()); + match.setEnd(tokens.get(match.getEnd()).getEnd()); + match.setText(utterance.substring(match.getStart(), match.getEnd() + 1)); + results.add(match); + } + } + + // Return the results sorted by position in the utterance + results.sort((a, b) -> a.getStart() - b.getStart()); + return results; + } + + private static int indexOfToken(List tokens, Token token, int startPos) { + for (int i = startPos; i < tokens.size(); i++) { + if (StringUtils.equalsIgnoreCase(tokens.get(i).getNormalized(), token.getNormalized())) { + return i; + } + } + + return -1; + } + + private static ModelResult matchValue( + List sourceTokens, + int maxDistance, + FindValuesOptions options, + int index, + String value, + List searchedTokens, + int startPos + ) { + // Match value to utterance and calculate total deviation. + // - The tokens are matched in order so "second last" will match in + // "the second from last one" but not in "the last from the second one". + // - The total deviation is a count of the number of tokens skipped in the + // match so for the example above the number of tokens matched would be + // 2 and the total deviation would be 1. + int matched = 0; + int totalDeviation = 0; + int start = -1; + int end = -1; + for (Token token : searchedTokens) { + // Find the position of the token in the utterance. + int pos = indexOfToken(sourceTokens, token, startPos); + if (pos >= 0) { + // Calculate the distance between the current tokens position and the + // previous tokens distance. + int distance = matched > 0 ? pos - startPos : 0; + if (distance <= maxDistance) { + // Update count of tokens matched and move start pointer to search + // for next token after the current token. + matched++; + totalDeviation += distance; + startPos = pos + 1; + + // Update start & end position that will track the span of the utterance + // that's matched. + if (start < 0) { + start = pos; + } + + end = pos; + } + } + } + + // Calculate score and format result + // - The start & end positions and the results text field will be corrected by the caller. + ModelResult result = null; + + if (matched > 0 && (matched == searchedTokens.size() || options.getAllowPartialMatches())) { + // Percentage of tokens matched. If matching "second last" in + // "the second from last one" the completeness would be 1.0 since + // all tokens were found. + int completeness = matched / searchedTokens.size(); + + // Accuracy of the match. The accuracy is reduced by additional tokens + // occurring in the value that weren't in the utterance. So an utterance + // of "second last" matched against a value of "second from last" would + // result in an accuracy of 0.5. + float accuracy = (float) matched / (matched + totalDeviation); + + // The final score is simply the completeness multiplied by the accuracy. + float score = completeness * accuracy; + + // Format result + result = new ModelResult<>(); + result.setStart(start); + result.setEnd(end); + result.setTypeName("value"); + result.setResolution(new FoundValue() {{ + setValue(value); + setIndex(index); + setScore(score); + }}); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java new file mode 100644 index 000000000..ff62e7296 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Contains options to control how input is matched against a list of choices. + */ +public class FindChoicesOptions extends FindValuesOptions { + @JsonProperty(value = "noValue") + private boolean noValue; + + @JsonProperty(value = "noAction") + private boolean noAction; + + @JsonProperty(value = "recognizeNumbers") + private boolean recognizeNumbers = true; + + @JsonProperty(value = "recognizeOrdinals") + private boolean recognizeOrdinals; + + /** + * Indicates whether the choices value will NOT be search over. The default is false. + * @return true if the choices value will NOT be search over. + */ + public boolean isNoValue() { + return noValue; + } + + /** + * Sets whether the choices value will NOT be search over. + * @param withNoValue true if the choices value will NOT be search over. + */ + public void setNoValue(boolean withNoValue) { + noValue = withNoValue; + } + + /** + * Indicates whether the title of the choices action will NOT be searched over. The default + * is false. + * @return true if the title of the choices action will NOT be searched over. + */ + public boolean isNoAction() { + return noAction; + } + + /** + * Sets whether the title of the choices action will NOT be searched over. + * @param withNoAction true if the title of the choices action will NOT be searched over. + */ + public void setNoAction(boolean withNoAction) { + noAction = withNoAction; + } + + /** + * Indicates whether the recognizer should check for Numbers using the NumberRecognizer's + * NumberModel. + * @return Default is true. If false, the Number Model will not be used to check the + * utterance for numbers. + */ + public boolean isRecognizeNumbers() { + return recognizeNumbers; + } + + /** + * Set whether the recognizer should check for Numbers using the NumberRecognizer's + * NumberModel. + * @param withRecognizeNumbers Default is true. If false, the Number Model will not be + * used to check the utterance for numbers. + */ + public void setRecognizeNumbers(boolean withRecognizeNumbers) { + recognizeNumbers = withRecognizeNumbers; + } + + /** + * Indicates whether the recognizer should check for Ordinal Numbers using the NumberRecognizer's + * OrdinalModel. + * @return Default is true. If false, the Ordinal Model will not be used to check the + * utterance for ordinal numbers. + */ + public boolean isRecognizeOrdinals() { + return recognizeOrdinals; + } + + /** + * Sets whether the recognizer should check for Ordinal Numbers using the NumberRecognizer's + * OrdinalModel. + * @param withRecognizeOrdinals If false, the Ordinal Model will not be used to check the + * utterance for ordinal numbers. + */ + public void setRecognizeOrdinals(boolean withRecognizeOrdinals) { + recognizeOrdinals = withRecognizeOrdinals; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java new file mode 100644 index 000000000..d45e88816 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Locale; + +/** + * Contains options used to control how choices are recognized in a users utterance. + */ +public class FindValuesOptions { + @JsonProperty(value = "allowPartialMatches") + private boolean allowPartialMatches; + + @JsonProperty(value = "locale") + private String locale = Locale.ENGLISH.getDisplayName(); + + @JsonProperty(value = "maxTokenDistance") + private int maxTokenDistance = 2; + + @JsonProperty(value = "tokenizer") + private TokenizerFunction tokenizer; + + /** + * Gets value indicating whether only some of the tokens in a value need to exist to be + * considered. + * @return true if only some of the tokens in a value need to exist to be considered; + * otherwise false. + */ + public boolean getAllowPartialMatches() { + return allowPartialMatches; + } + + /** + * Sets value indicating whether only some of the tokens in a value need to exist to be + * considered. + * @param withAllowPartialMatches true if only some of the tokens in a value need to exist + * to be considered; otherwise false. + */ + public void setAllowPartialMatches(boolean withAllowPartialMatches) { + allowPartialMatches = withAllowPartialMatches; + } + + /** + * Gets the locale/culture code of the utterance. The default is `en-US`. This is optional. + * @return The locale/culture code of the utterance. + */ + public String getLocale() { + return locale; + } + + /** + * Sets the locale/culture code of the utterance. The default is `en-US`. This is optional. + * @param withLocale The locale/culture code of the utterance. + */ + public void setLocale(String withLocale) { + locale = withLocale; + } + + /** + * Gets the maximum tokens allowed between two matched tokens in the utterance. So with + * a max distance of 2 the value "second last" would match the utterance "second from the last" + * but it wouldn't match "Wait a second. That's not the last one is it?". + * The default value is "2". + * @return The maximum tokens allowed between two matched tokens in the utterance. + */ + public int getMaxTokenDistance() { + return maxTokenDistance; + } + + /** + * Gets the maximum tokens allowed between two matched tokens in the utterance. So with + * a max distance of 2 the value "second last" would match the utterance "second from the last" + * but it wouldn't match "Wait a second. That's not the last one is it?". + * The default value is "2". + * @param withMaxTokenDistance The maximum tokens allowed between two matched tokens in the + * utterance. + */ + public void setMaxTokenDistance(int withMaxTokenDistance) { + maxTokenDistance = withMaxTokenDistance; + } + + /** + * Gets the tokenizer to use when parsing the utterance and values being recognized. + * @return The tokenizer to use when parsing the utterance and values being recognized. + */ + public TokenizerFunction getTokenizer() { + return tokenizer; + } + + /** + * Sets the tokenizer to use when parsing the utterance and values being recognized. + * @param withTokenizer The tokenizer to use when parsing the utterance and values being + * recognized. + */ + public void setTokenizer(TokenizerFunction withTokenizer) { + tokenizer = withTokenizer; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java new file mode 100644 index 000000000..f1e8351f0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a result from matching user input against a list of choices. + */ +public class FoundChoice { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "index") + private int index; + + @JsonProperty(value = "score") + private float score; + + @JsonProperty(value = "synonym") + private String synonym; + + /** + * Gets the value that was matched. + * @return The value that was matched. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that was matched. + * @param withValue The value that was matched. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the index of the value that was matched. + * @return The index of the value that was matched. + */ + public int getIndex() { + return index; + } + + /** + * Sets the index of the value that was matched. + * @param withIndex The index of the value that was matched. + */ + public void setIndex(int withIndex) { + index = withIndex; + } + + /** + * Gets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @return The accuracy with which the value matched the specified portion of the utterance. + * A value of 1.0 would indicate a perfect match. + */ + public float getScore() { + return score; + } + + /** + * Sets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @param withScore The accuracy with which the value matched the specified portion of the + * utterance. A value of 1.0 would indicate a perfect match. + */ + public void setScore(float withScore) { + score = withScore; + } + + /** + * Gets the synonym that was matched. This is optional. + * @return The synonym that was matched. + */ + public String getSynonym() { + return synonym; + } + + /** + * Sets the synonym that was matched. This is optional. + * @param withSynonym The synonym that was matched. + */ + public void setSynonym(String withSynonym) { + synonym = withSynonym; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java new file mode 100644 index 000000000..9bd3430bf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class is internal and should not be used. + * Please use FoundChoice instead. + */ +class FoundValue { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "index") + private int index; + + @JsonProperty(value = "score") + private float score; + + /** + * Gets the value that was matched. + * @return The value that was matched. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that was matched. + * @param withValue The value that was matched. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the index of the value that was matched. + * @return The index of the value that was matched. + */ + public int getIndex() { + return index; + } + + /** + * Sets the index of the value that was matched. + * @param withIndex The index of the value that was matched. + */ + public void setIndex(int withIndex) { + index = withIndex; + } + + /** + * Gets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @return The accuracy with which the value matched the specified portion of the utterance. + * A value of 1.0 would indicate a perfect match. + */ + public float getScore() { + return score; + } + + /** + * Sets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @param withScore The accuracy with which the value matched the specified portion of the + * utterance. A value of 1.0 would indicate a perfect match. + */ + public void setScore(float withScore) { + score = withScore; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java new file mode 100644 index 000000000..fbc5316c1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +/** + * Controls the way that choices for a `ChoicePrompt` or yes/no options for a `ConfirmPrompt` are + * presented to a user. + */ +public enum ListStyle { + /// Don't include any choices for prompt. + NONE, + + /// Automatically select the appropriate style for the current channel. + AUTO, + + /// Add choices to prompt as an inline list. + INLINE, + + /// Add choices to prompt as a numbered list. + LIST, + + /// Add choices to prompt as suggested actions. + SUGGESTED_ACTION, + + /// Add choices to prompt as a HeroCard with buttons. + HEROCARD +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java new file mode 100644 index 000000000..ff04b5c28 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +/** + * Contains recognition result information. + * + * @param The type of object recognized. + */ +public class ModelResult { + private String text; + private int start; + private int end; + private String typeName; + private T resolution; + + /** + * Gets the substring of the input that was recognized. + * + * @return The substring of the input that was recognized. + */ + public String getText() { + return text; + } + + /** + * Sets the substring of the input that was recognized. + * + * @param withText The substring of the input that was recognized. + */ + public void setText(String withText) { + text = withText; + } + + /** + * Gets the start character position of the recognized substring. + * @return The start character position of the recognized substring. + */ + public int getStart() { + return start; + } + + /** + * Sets the start character position of the recognized substring. + * @param withStart The start character position of the recognized substring. + */ + public void setStart(int withStart) { + start = withStart; + } + + /** + * Gets the end character position of the recognized substring. + * @return The end character position of the recognized substring. + */ + public int getEnd() { + return end; + } + + /** + * Starts the end character position of the recognized substring. + * @param withEnd The end character position of the recognized substring. + */ + public void setEnd(int withEnd) { + end = withEnd; + } + + /** + * Gets the type of entity that was recognized. + * @return The type of entity that was recognized. + */ + public String getTypeName() { + return typeName; + } + + /** + * Sets the type of entity that was recognized. + * @param withTypeName The type of entity that was recognized. + */ + public void setTypeName(String withTypeName) { + typeName = withTypeName; + } + + /** + * Gets the recognized object. + * @return The recognized object. + */ + public T getResolution() { + return resolution; + } + + /** + * Sets the recognized object. + * @param withResolution The recognized object. + */ + public void setResolution(T withResolution) { + resolution = withResolution; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java new file mode 100644 index 000000000..7915ddb05 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A value that can be sorted and still refer to its original position with a source array. + */ +public class SortedValue { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "index") + private int index; + + /** + * Creates a sort value. + * @param withValue The value that will be sorted. + * @param withIndex The values original position within its unsorted array. + */ + public SortedValue(String withValue, int withIndex) { + value = withValue; + index = withIndex; + } + + /** + * Gets the value that will be sorted. + * @return The value that will be sorted. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that will be sorted. + * @param withValue The value that will be sorted. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the values original position within its unsorted array. + * @return The values original position within its unsorted array. + */ + public int getIndex() { + return index; + } + + /** + * Sets the values original position within its unsorted array. + * @param withIndex The values original position within its unsorted array. + */ + public void setIndex(int withIndex) { + index = withIndex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java new file mode 100644 index 000000000..02431564f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +/** + * Represents an individual token, such as a word in an input string. + */ +public class Token { + private String text; + private int start; + private int end; + private String normalized; + + /** + * Gets the original text of the token. + * + * @return The original text of the token. + */ + public String getText() { + return text; + } + + /** + * Sets the original text of the token. + * + * @param withText The original text of the token. + */ + public void setText(String withText) { + text = withText; + } + + /** + * Appends a string to the text value. + * @param withText The text to append. + */ + public void appendText(String withText) { + if (text != null) { + text += withText; + } else { + text = withText; + } + } + + /** + * Gets the index of the first character of the token within the input. + * @return The index of the first character of the token. + */ + public int getStart() { + return start; + } + + /** + * Sets the index of the first character of the token within the input. + * @param withStart The index of the first character of the token. + */ + public void setStart(int withStart) { + start = withStart; + } + + /** + * Gets the index of the last character of the token within the input. + * @return The index of the last character of the token. + */ + public int getEnd() { + return end; + } + + /** + * Starts the index of the last character of the token within the input. + * @param withEnd The index of the last character of the token. + */ + public void setEnd(int withEnd) { + end = withEnd; + } + + /** + * Gets the normalized version of the token. + * @return A normalized version of the token. + */ + public String getNormalized() { + return normalized; + } + + /** + * Sets the normalized version of the token. + * @param withNormalized A normalized version of the token. + */ + public void setNormalized(String withNormalized) { + normalized = withNormalized; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java new file mode 100644 index 000000000..e7f6c4258 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides the default Tokenizer implementation. + */ +public class Tokenizer implements TokenizerFunction { + + /** + * Simple tokenizer that breaks on spaces and punctuation. The only normalization + * done is to lowercase. + * @param text The input text. + * @param locale Optional, identifies the locale of the input text. + * @return The list of the found Token objects. + */ + @SuppressWarnings("checkstyle:MagicNumber") + @Override + public List tokenize(String text, String locale) { + List tokens = new ArrayList<>(); + Token token = null; + + int length = text == null ? 0 : text.length(); + int i = 0; + + while (i < length) { + int codePoint = text.codePointAt(i); + + String chr = new String(Character.toChars(codePoint)); + + if (isBreakingChar(codePoint)) { + appendToken(tokens, token, i - 1); + token = null; + } else if (codePoint > 0xFFFF) { + appendToken(tokens, token, i - 1); + token = null; + + Token t = new Token(); + t.setStart(i); + t.setEnd(i + chr.length() - 1); + t.setText(chr); + t.setNormalized(chr); + + tokens.add(t); + } else if (token == null) { + token = new Token(); + token.setStart(i); + token.setText(chr); + } else { + token.appendText(chr); + } + + i += chr.length(); + } + + appendToken(tokens, token, length - 1); + return tokens; + } + + private void appendToken(List tokens, Token token, int end) { + if (token != null) { + token.setEnd(end); + token.setNormalized(token.getText().toLowerCase()); + tokens.add(token); + } + } + + @SuppressWarnings("checkstyle:MagicNumber") + private static boolean isBreakingChar(int codePoint) { + return isBetween(codePoint, 0x0000, 0x002F) + || isBetween(codePoint, 0x003A, 0x0040) + || isBetween(codePoint, 0x005B, 0x0060) + || isBetween(codePoint, 0x007B, 0x00BF) + || isBetween(codePoint, 0x02B9, 0x036F) + || isBetween(codePoint, 0x2000, 0x2BFF) + || isBetween(codePoint, 0x2E00, 0x2E7F); + } + + private static boolean isBetween(int value, int from, int to) { + return value >= from && value <= to; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java new file mode 100644 index 000000000..3fb3c459d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.List; + +/** + * Represents a callback method that can break a string into its component tokens. + */ +@FunctionalInterface +public interface TokenizerFunction { + + /** + * The callback method that can break a string into its component tokens. + * @param text The input text. + * @param locale Optional, identifies the locale of the input text. + * @return The list of the found Token objects. + */ + List tokenize(String text, String locale); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java new file mode 100644 index 000000000..7420d4796 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.choices; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java new file mode 100644 index 000000000..f7e1fe0b8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; + +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; + +/** + * Defines Component Memory Scopes interface for enumerating memory scopes. + */ +public interface ComponentMemoryScopes { + + /** + * Gets the memory scopes. + * + * @return A reference with the memory scopes. + */ + Iterable getMemoryScopes(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java new file mode 100644 index 000000000..057a90629 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; +/** + * Interface for declaring path resolvers in the memory system. + */ +public interface ComponentPathResolvers { + /** + * Return enumeration of pathresolvers. + * + * @return collection of PathResolvers. + */ + Iterable getPathResolvers(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java new file mode 100644 index 000000000..aab062cee --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java @@ -0,0 +1,834 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.ComponentRegistration; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogPath; +import com.microsoft.bot.dialogs.DialogsComponentRegistration; +import com.microsoft.bot.dialogs.ObjectPath; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; +import com.microsoft.bot.schema.ResultPair; + +import org.apache.commons.lang3.StringUtils; + +/** + * The DialogStateManager manages memory scopes and pathresolvers MemoryScopes + * are named root level Objects, which can exist either in the dialogcontext or + * off of turn state PathResolvers allow for shortcut behavior for mapping + * things like $foo -> dialog.foo. + */ +public class DialogStateManager implements Map { + + /** + * Information for tracking when path was last modified. + */ + private final String pathTracker = "dialog._tracker.paths"; + + private static final char[] SEPARATORS = {',', '['}; + + private final DialogContext dialogContext; + private int version; + + /** + * Initializes a new instance of the + * {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class. + * + * @param dc The dialog context for the current turn of the + * conversation. + * @param configuration Configuration for the dialog state manager. + */ + public DialogStateManager(DialogContext dc, DialogStateManagerConfiguration configuration) { + ComponentRegistration.add(new DialogsComponentRegistration()); + + if (dc != null) { + dialogContext = dc; + } else { + throw new IllegalArgumentException("dc cannot be null."); + } + + if (configuration != null) { + this.configuration = configuration; + } else { + this.configuration = dc.getContext().getTurnState().get(DialogStateManagerConfiguration.class.getName()); + } + + if (this.configuration == null) { + this.configuration = new DialogStateManagerConfiguration(); + + Iterable components = ComponentRegistration.getComponents(); + + components.forEach((component) -> { + if (component instanceof ComponentMemoryScopes) { + ((ComponentMemoryScopes) component).getMemoryScopes().forEach((scope) -> { + this.configuration.getMemoryScopes().add(scope); + }); + } + if (component instanceof ComponentPathResolvers) { + ((ComponentPathResolvers) component).getPathResolvers().forEach((pathResolver) -> { + this.configuration.getPathResolvers().add(pathResolver); + }); + } + }); + } + // cache for any other new dialogStatemanager instances in this turn. + dc.getContext().getTurnState().replace(this.configuration); + } + + private DialogStateManagerConfiguration configuration; + + /** + * Sets the configured path resolvers and memory scopes for the dialog. + * + * @return The DialogStateManagerConfiguration. + */ + public DialogStateManagerConfiguration getConfiguration() { + return configuration; + } + + /** + * Sets the configured path resolvers and memory scopes for the dialog. + * + * @param withDialogStateManagerConfiguration The configuration to set. + */ + public void setConfiguration(DialogStateManagerConfiguration withDialogStateManagerConfiguration) { + this.configuration = withDialogStateManagerConfiguration; + } + + /** + * Gets a value indicating whether the dialog state manager is read-only. + * + * @return true + */ + public Boolean getIsReadOnly() { + return true; + } + + /** + * Gets the elements with the specified key. + * + * @param key Key to get or set the element. + * @return The element with the specified key. + */ + public Object getElement(String key) { + // return GetValue(key); + return null; + } + + /** + * Sets the elements with the specified key. + * + * @param key Key to get or set the element. + * @param element The element to store with the provided key. + */ + public void setElement(String key, Object element) { + if (key.indexOf(SEPARATORS[0]) == -1 && key.indexOf(SEPARATORS[1]) == -1) { + MemoryScope scope = getMemoryScope(key); + if (scope != null) { + ObjectMapper mapper = new ObjectMapper(); + try { + scope.setMemory(dialogContext, mapper.writeValueAsString(element)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } else { + throw new IllegalArgumentException(); + } + } + } + + /** + * Get MemoryScope by name. + * + * @param name Name of scope. + * @return A memory scope. + */ + public MemoryScope getMemoryScope(String name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null."); + } + return configuration.getMemoryScopes().stream().filter((scope) -> scope.getName().equalsIgnoreCase(name)) + .findFirst().get(); + } + + /** + * Version help caller to identify the updates and decide cache or not. + * + * @return Current version + */ + public String version() { + return Integer.toString(version); + } + + /** + * ResolveMemoryScope will find the MemoryScope for and return the remaining + * path. + * + * @param path Incoming path to resolve to scope and remaining path. + * @param remainingPath Remaining subpath in scope. + * @return The memory scope. + */ + public MemoryScope resolveMemoryScope(String path, StringBuilder remainingPath) { + String scope = path; + int sepIndex = -1; + int dot = StringUtils.indexOfIgnoreCase(path, "."); + int openSquareBracket = StringUtils.indexOfIgnoreCase(path, "["); + + if (dot > 0 && openSquareBracket > 0) { + sepIndex = Math.min(dot, openSquareBracket); + } else if (dot > 0) { + sepIndex = dot; + } else if (openSquareBracket > 0) { + sepIndex = openSquareBracket; + } + + if (sepIndex > 0) { + scope = path.substring(0, sepIndex); + MemoryScope memoryScope = getMemoryScope(scope); + if (memoryScope != null) { + remainingPath.append(path.substring(sepIndex + 1)); + return memoryScope; + } + } + + MemoryScope resultScope = getMemoryScope(scope); + if (resultScope == null) { + throw new IllegalArgumentException(getBadScopeMessage(path)); + } else { + return resultScope; + } + } + + /** + * Transform the path using the registered PathTransformers. + * + * @param path Path to transform. + * @return The transformed path. + */ + public String transformPath(String path) { + List resolvers = configuration.getPathResolvers(); + + for (PathResolver resolver : resolvers) { + path = resolver.transformPath(path); + } + + return path; + } + + /** + * Get the value from memory using path expression (NOTE: This always returns + * clone of value). + * + * @param the value type to return. + * @param path >path expression to use. + * @param clsType the Type that is being requested as a result + * @return ResultPair with boolean and requested type TypeT as a result + */ + public ResultPair tryGetValue(String path, Class clsType) { + TypeT instance = null; + + if (path == null) { + throw new IllegalArgumentException("path cannot be null"); + } + + path = transformPath(path); + + MemoryScope memoryScope = null; + StringBuilder remainingPath = new StringBuilder(); + + try { + memoryScope = resolveMemoryScope(path, remainingPath); + } catch (Exception err) { + // Trace.TraceError(err.Message); + return new ResultPair<>(false, instance); + } + + if (memoryScope == null) { + return new ResultPair<>(false, instance); + } + + if (remainingPath.length() == 0) { + Object memory = memoryScope.getMemory(dialogContext); + if (memory == null) { + return new ResultPair<>(false, instance); + } + + instance = (TypeT) ObjectPath.mapValueTo(memory, clsType); + + return new ResultPair<>(true, instance); + } + + // HACK to support .First() retrieval on turn.recognized.entities.foo, + // replace with Expressions + // once expression ship + final String first = ".FIRST()"; + int iFirst = path.toUpperCase(Locale.US).lastIndexOf(first); + if (iFirst >= 0) { + remainingPath = new StringBuilder(path.substring(iFirst + first.length())); + path = path.substring(0, iFirst); + ResultPair getResult = tryGetFirstNestedValue(new AtomicReference(path), this); + if (getResult.result()) { + if (StringUtils.isEmpty(remainingPath.toString())) { + instance = (TypeT) ObjectPath.mapValueTo(getResult.getRight(), clsType); + return new ResultPair<>(true, instance); + } + instance = (TypeT) ObjectPath.tryGetPathValue(getResult.getRight(), remainingPath.toString(), clsType); + + return new ResultPair<>(true, instance); + } + + return new ResultPair<>(false, instance); + } + + instance = (TypeT) ObjectPath.tryGetPathValue(this, path, clsType); + + return new ResultPair<>(instance != null, instance); + } + + /** + * Get the value from memory using path expression (NOTE: This always returns + * clone of value). + * + * @param The value type to return. + * @param pathExpression Path expression to use. + * @param defaultValue Default value to return if there is none found. + * @param clsType Type of value that is being requested as a return. + * @return Result or the default value if the path is not valid. + */ + public T getValue(String pathExpression, T defaultValue, Class clsType) { + if (pathExpression == null) { + throw new IllegalArgumentException("path cannot be null"); + } + + ResultPair result = tryGetValue(pathExpression, clsType); + if (result.result()) { + return result.value(); + } else { + return defaultValue; + } + } + + /** + * Get a int value from memory using a path expression. + * + * @param pathExpression Path expression. + * @param defaultValue Default value if the value doesn't exist. + * @return Value or default value if path is not valid. + */ + public int getIntValue(String pathExpression, int defaultValue) { + if (pathExpression == null) { + throw new IllegalArgumentException("path cannot be null"); + } + + ResultPair result = tryGetValue(pathExpression, Integer.class); + if (result.result()) { + return result.value(); + } else { + return defaultValue; + } + } + + /** + * Get a boolean value from memory using a path expression. + * + * @param pathExpression Path expression. + * @param defaultValue Default value if the value doesn't exist. + * @return Value or default value if path is not valid. + */ + public Boolean getBoolValue(String pathExpression, Boolean defaultValue) { + if (pathExpression == null || StringUtils.isEmpty(pathExpression)) { + throw new IllegalArgumentException("path cannot be null"); + } + + ResultPair result = tryGetValue(pathExpression, Boolean.class); + if (result.result()) { + return result.value(); + } else { + return defaultValue; + } + } + + /** + * Get a String value from memory using a path expression. + * + * @param pathExpression The path expression. + * @param defaultValue Default value if the value doesn't exist. + * @return String or default value if path is not valid. + */ + public String getStringValue(String pathExpression, String defaultValue) { + return getValue(pathExpression, defaultValue, String.class); + } + + /** + * Set memory to value. + * + * @param path Path to memory. + * @param value Object to set. + */ + public void setValue(String path, Object value) { + if (value instanceof CompletableFuture) { + throw new IllegalArgumentException( + String.format("%s = You can't pass an unresolved CompletableFuture to SetValue", path)); + } + + if (path == null || StringUtils.isEmpty(path)) { + throw new IllegalArgumentException("path cannot be null"); + } + + if (value != null) { + ObjectMapper mapper = new ObjectMapper(); + value = mapper.valueToTree(value); + } + + path = transformPath(path); + if (trackChange(path, value)) { + ObjectPath.setPathValue(this, path, value); + } + + // Every set will increase version + version++; + } + + /** + * Remove property from memory. + * + * @param path Path to remove the leaf property. + */ + public void removeValue(String path) { + if (!StringUtils.isNotBlank(path)) { + throw new IllegalArgumentException("Path cannot be null"); + } + + path = transformPath(path); + if (trackChange(path, null)) { + ObjectPath.removePathValue(this, path); + } + } + + /** + * Gets all memoryscopes suitable for logging. + * + * @return JsonNode that which represents all memory scopes. + */ + public JsonNode getMemorySnapshot() { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode result = mapper.createObjectNode(); + + List scopes = configuration.getMemoryScopes().stream().filter((x) -> x.getIncludeInSnapshot()) + .collect(Collectors.toList()); + for (MemoryScope scope : scopes) { + Object memory = scope.getMemory(dialogContext); + if (memory != null) { + try { + result.put(scope.getName(), mapper.writeValueAsString(memory)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + } + return result; + } + + /** + * Load all of the scopes. + * + * @return A Completed Future. + */ + public CompletableFuture loadAllScopesAsync() { + configuration.getMemoryScopes().forEach((scope) -> { + scope.load(dialogContext, false).join(); + }); + return CompletableFuture.completedFuture(null); + } + + /** + * Save all changes for all scopes. + * + * @return Completed Future + */ + public CompletableFuture saveAllChanges() { + configuration.getMemoryScopes().forEach((memoryScope) -> { + memoryScope.saveChanges(dialogContext, false).join(); + }); + return CompletableFuture.completedFuture(null); + } + + /** + * Delete the memory for a scope. + * + * @param name name of the scope + * @return Completed CompletableFuture + */ + public CompletableFuture deleteScopesMemory(String name) { + // Make a copy here that is final so it can be used in lamdba expression below + final String uCaseName = name.toUpperCase(); + MemoryScope scope = configuration.getMemoryScopes().stream().filter((s) -> { + return s.getName().toUpperCase() == uCaseName; + }).findFirst().get(); + if (scope != null) { + scope.delete(dialogContext).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Adds an element to the dialog state manager. + * + * @param key Key of the element to add. + * @param value Value of the element to add. + */ + public void add(String key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Determines whether the dialog state manager contains an element with the + * specified key. + * + * @param key The key to locate in the dialog state manager. + * @return true if the dialog state manager contains an element with the key; + * otherwise, false. + */ + public Boolean containsKey(String key) { + for (MemoryScope scope : configuration.getMemoryScopes()) { + if (scope.getName().toUpperCase().equals(key.toUpperCase())) { + return true; + } + } + return false; + } + + /** + * Removes the element with the specified key from the dialog state manager. + * + * @param key The key of the element to remove. + * @return true if the element is succesfully removed; otherwise, false. + */ + public Boolean remove(String key) { + throw new UnsupportedOperationException(); + } + + /** + * Gets the value associated with the specified key. + * + * @param key The key whose value to get. + * @param value When this method returns, the value associated with the + * specified key, if the key is found; otherwise, the default value + * for the type of the value parameter. + * @return true if the dialog state manager contains an element with the + * specified key; + */ + public ResultPair tryGetValue(String key, Object value) { + return tryGetValue(key, Object.class); + } + + /** + * Adds an item to the dialog state manager. + * + * @param item The SimpleEntry with the key and Object of the item to add. + */ + public void add(SimpleEntry item) { + throw new UnsupportedOperationException(); + } + + /** + * Removes all items from the dialog state manager. + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Determines whether the dialog state manager contains a specific value. + * + * @param item The of the item to locate. + * @return True if item is found in the dialog state manager; otherwise,false + */ + public Boolean contains(SimpleEntry item) { + throw new UnsupportedOperationException(); + } + + /** + * Copies the elements of the dialog state manager to an array starting at a + * particular index. + * + * @param array The one-dimensional array that is the destination of the + * elements copiedfrom the dialog state manager. The array + * must have zero-based indexing. + * @param arrayIndex The zero-based index in array at which copying begins. + */ + public void copyTo(SimpleEntry[] array, int arrayIndex) { + for (MemoryScope scope : configuration.getMemoryScopes()) { + array[arrayIndex++] = new SimpleEntry(scope.getName(), scope.getMemory(dialogContext)); + } + } + + /// + /// Removes the first occurrence of a specific Object from the dialog state + /// manager. + /// + /// The Object to remove from the dialog state + /// manager. + /// true if the item was successfully removed from the dialog + /// state manager; + /// otherwise, false. + /// This method is not supported. + /** + * Removes the first occurrence of a specific Object from the dialog state + * manager. + * + * @param item The Object to remove from the dialog state manager. + * @return true if the item was successfully removed from the dialog state + * manager otherwise false + */ + public boolean remove(SimpleEntry item) { + throw new UnsupportedOperationException(); + } + + /** + * Returns an Iterator that iterates through the collection. + * + * @return An Iterator that can be used to iterate through the collection. + */ + public Iterable> getEnumerator() { + List> resultList = new ArrayList>(); + for (MemoryScope scope : configuration.getMemoryScopes()) { + resultList.add(new SimpleEntry(scope.getName(), scope.getMemory(dialogContext))); + } + return resultList; + } + + /** + * Track when specific paths are changed. + * + * @param paths Paths to track. + * @return Normalized paths to pass to AnyPathChanged/>. + */ + public List trackPaths(Iterable paths) { + List allPaths = new ArrayList(); + for (String path : allPaths) { + String tpath = transformPath(path); + // Track any path that resolves to a constant path + Object resolved = ObjectPath.tryResolvePath(this, tpath); + if (resolved != null) { + String npath = String.join("_", resolved.toString()); + setValue(pathTracker + "." + npath, 0); + allPaths.add(npath); + } + } + return allPaths; + } + + /** + * Check to see if any path has changed since watermark. + * + * @param counter Time counter to compare to. + * @param paths Paths from Trackpaths to check. + * @return True if any path has changed since counter. + */ + public Boolean anyPathChanged(int counter, Iterable paths) { + Boolean found = false; + if (paths != null) { + for (String path : paths) { + int resultValue = getValue(pathTracker + "." + path, -1, Integer.class); + if (resultValue != -1 && resultValue > counter) { + found = true; + break; + } + } + } + return found; + } + + @SuppressWarnings("PMD.UnusedFormalParameter") + private static ResultPair tryGetFirstNestedValue(AtomicReference remainingPath, Object memory) { + ArrayNode array = new ArrayNode(JsonNodeFactory.instance); + Object value; + array = ObjectPath.tryGetPathValue(memory, remainingPath.get(), ArrayNode.class); + + if (array != null && array.size() > 0) { + JsonNode firstNode = array.get(0); + if (firstNode instanceof ArrayNode) { + if (firstNode.size() > 0) { + JsonNode secondNode = firstNode.get(0); + value = ObjectPath.mapValueTo(secondNode, Object.class); + return new ResultPair(true, value); + } + return new ResultPair(false, null); + } + value = ObjectPath.mapValueTo(firstNode, Object.class); + return new ResultPair(true, value); + } + return new ResultPair(false, null); + } + + private String getBadScopeMessage(String path) { + StringBuilder errorMessage = new StringBuilder(path); + errorMessage.append(" does not match memory scopes:["); + List scopeNames = new ArrayList(); + List scopes = configuration.getMemoryScopes(); + scopes.forEach((sc) -> { + scopeNames.add(sc.getName()); + }); + errorMessage.append(String.join(",", scopeNames)); + errorMessage.append("]"); + return errorMessage.toString(); + } + + private Boolean trackChange(String path, Object value) { + Boolean hasPath = false; + ArrayList segments = ObjectPath.tryResolvePath(this, path, false); + if (segments != null) { + String root = segments.size() > 1 ? (String) segments.get(1) : new String(); + + // Skip _* as first scope, i.e. _adaptive, _tracker, ... + if (!root.startsWith("_")) { + List stringSegments = segments.stream().map(Object -> Objects.toString(Object, null)) + .collect(Collectors.toList()); + + // Convert to a simple path with _ between segments + String pathName = String.join("_", stringSegments); + String trackedPath = String.format("%s.%s", pathTracker, pathName); + Integer counter = getValue(DialogPath.EVENTCOUNTER, 0, Integer.class); + /** + * + */ + ResultPair result = tryGetValue(trackedPath, Integer.class); + if (result.result()) { + if (counter == null) { + counter = getValue(DialogPath.EVENTCOUNTER, 0, Integer.class); + } + setValue(trackedPath, counter); + } + if (value instanceof Map) { + final int count = counter; + ((Map) value).forEach((key, val) -> { + checkChildren(key, val, trackedPath, count); + }); + } else if (value instanceof ObjectNode) { + ObjectNode node = (ObjectNode) value; + Iterator fields = node.fieldNames(); + + while (fields.hasNext()) { + String field = fields.next(); + checkChildren(field, node.findValue(field), trackedPath, counter); + } + } + } + hasPath = true; + } + return hasPath; + } + + private void checkChildren(String property, Object instance, String path, Integer counter) { + // Add new child segment + String trackedPath = path + "_" + property.toLowerCase(); + ResultPair pathCheck = tryGetValue(trackedPath, Integer.class); + if (pathCheck.result()) { + if (counter == null) { + counter = getValue(DialogPath.EVENTCOUNTER, 0, Integer.class); + } + setValue(trackedPath, counter); + } + + if (instance instanceof Map) { + final int count = counter; + ((Map) instance).forEach((key, value) -> { + checkChildren(key, value, trackedPath, count); + }); + } else if (instance instanceof ObjectNode) { + ObjectNode node = (ObjectNode) instance; + Iterator fields = node.fieldNames(); + + while (fields.hasNext()) { + String field = fields.next(); + checkChildren(field, node.findValue(field), trackedPath, counter); + } + } + + } + + @Override + public final int size() { + return configuration.getMemoryScopes().size(); + } + + @Override + public final boolean isEmpty() { + return size() == 0; + } + + @Override + public final boolean containsKey(Object key) { + return false; + } + + @Override + public final boolean containsValue(Object value) { + return false; + } + + @Override + public final Object get(Object key) { + return tryGetValue(key.toString(), Object.class).value(); + } + + @Override + public final Object put(String key, Object value) { + return null; + } + + @Override + public final Object remove(Object key) { + return null; + } + + @Override + public final void putAll(Map m) { + } + + + @Override + public final Set keySet() { + return configuration.getMemoryScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet()); + } + + @Override + public final Collection values() { + return configuration.getMemoryScopes().stream().map(scope -> scope.getMemory(dialogContext)) + .collect(Collectors.toSet()); + } + + @Override + public final Set> entrySet() { + Set> resultSet = new HashSet>(); + configuration.getMemoryScopes().forEach((scope) -> { + resultSet.add(new AbstractMap.SimpleEntry(scope.getName(), scope.getMemory(dialogContext))); + }); + + return resultSet; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java new file mode 100644 index 000000000..0d39d3547 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; + +/** + * Configures the path resolvers and memory scopes for the dialog state manager. + */ +public class DialogStateManagerConfiguration { + + private List pathResolvers = new ArrayList(); + + private List memoryScopes = new ArrayList(); + + + /** + * @return List Returns the list of PathResolvers. + */ + public List getPathResolvers() { + return this.pathResolvers; + } + + + /** + * @param withPathResolvers Set the list of PathResolvers. + */ + public void setPathResolvers(List withPathResolvers) { + this.pathResolvers = withPathResolvers; + } + + + /** + * @return List Returns the list of MemoryScopes. + */ + public List getMemoryScopes() { + return this.memoryScopes; + } + + + /** + * @param withMemoryScopes Set the list of MemoryScopes. + */ + public void setMemoryScopes(List withMemoryScopes) { + this.memoryScopes = withMemoryScopes; + } + + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java new file mode 100644 index 000000000..8a7c6c7e8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; +/** + * Defines Path Resolver interface for transforming paths. + */ +public interface PathResolver { + /** + * + * @param path path to inspect. + * @return transformed path. + */ + String transformPath(String path); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java new file mode 100644 index 000000000..7d3e4f99f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +import com.microsoft.bot.dialogs.memory.PathResolver; + +/** + * Maps aliasXXX -> path.xxx ($foo => dialog.foo). + */ +public class AliasPathResolver implements PathResolver { + + private final String postfix; + private final String prefix; + + /** + * + * @param alias Alias name. + * @param prefix Prefix name. + * @param postfix Postfix name. + */ + public AliasPathResolver(String alias, String prefix, String postfix) { + if (alias == null) { + throw new IllegalArgumentException("alias cannot be null"); + } + + if (prefix == null) { + throw new IllegalArgumentException("prefix cannot be null."); + } + + this.prefix = prefix.trim(); + + setAlias(alias.trim()); + + if (postfix == null) { + this.postfix = ""; + } else { + this.postfix = postfix; + } + } + + /** + * @return Gets the alias name. + */ + public String getAlias() { + return this.alias; + } + + /** + * @param alias Sets the alias name. + */ + private void setAlias(String alias) { + this.alias = alias; + } + + /** + * The alias name. + */ + private String alias; + + /** + * @param path Path to transform. + * @return The transformed path. + */ + public String transformPath(String path) { + if (path == null) { + throw new IllegalArgumentException("path cannot be null."); + } + + path = path.trim(); + if (path.startsWith(getAlias()) && path.length() > getAlias().length() + && isPathChar(path.charAt(getAlias().length()))) { + // here we only deals with trailing alias, alias in middle be handled in further + // breakdown + // $xxx -> path.xxx + return prefix + path.substring(getAlias().length()) + postfix; + } + + return path; + } + + /** + * + * @param ch Character to verify. + * @return true if the character is valid for a path; otherwise, false. + */ + protected Boolean isPathChar(char ch) { + return Character.isLetter(ch) || ch == '_'; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java new file mode 100644 index 000000000..df00ffa20 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps @@ => turn.recognized.entitites.xxx array. + */ +public class AtAtPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the AtAtPathResolver class. + */ + public AtAtPathResolver() { + super("@@", "turn.recognized.entities.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java new file mode 100644 index 000000000..170597619 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps @@ => turn.recognized.entitites.xxx array. + */ +public class AtPathResolver extends AliasPathResolver { + + private final String prefix = "turn.recognized.entities."; + + private static final char[] DELIMS = {'.', '[' }; + + /** + * Initializes a new instance of the AtPathResolver class. + */ + public AtPathResolver() { + super("@", "", null); + } + + /** + * Transforms the path. + * + * @param path Path to transform. + * @return The transformed path. + */ + @Override + public String transformPath(String path) { + if (path == null) { + throw new IllegalArgumentException("path cannot be null."); + } + + path = path.trim(); + if (path.startsWith("@") && path.length() > 1 && isPathChar(path.charAt(1))) { + int end = 0; + int endperiod = path.indexOf(DELIMS[0]); + int endbracket = path.indexOf(DELIMS[1]); + if (endperiod == -1 && endbracket == -1) { + end = path.length(); + } else { + end = Math.max(endperiod, endbracket); + } + + String property = path.substring(1, end); + String suffix = path.substring(end); + path = prefix + property + ".first()" + suffix; + } + + return path; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java new file mode 100644 index 000000000..ae455db19 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Resolve $xxx. + */ +public class DollarPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the DollarPathResolver class. + */ + public DollarPathResolver() { + super("$", "dialog.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java new file mode 100644 index 000000000..0f01b3a0c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps #xxx => turn.recognized.intents.xxx. + */ +public class HashPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the HashPathResolver class. + */ + public HashPathResolver() { + super("#", "turn.recognized.intents.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java new file mode 100644 index 000000000..6822bfff7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps %xxx => settings.xxx (aka activeDialog.Instance.xxx). + */ +public class PercentPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the PercentPathResolver class. + */ + public PercentPathResolver() { + super("%", "class.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java new file mode 100644 index 000000000..649aa0346 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.memory.pathresolvers; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java new file mode 100644 index 000000000..c4737916c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.memory; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java new file mode 100644 index 000000000..fbf2224cf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.BotState.CachedBotState; +import com.microsoft.bot.dialogs.DialogContext; + +/** + * BotStateMemoryScope represents a BotState scoped memory. + * + * @param The BotState type. + */ +public class BotStateMemoryScope extends MemoryScope { + + private Class type; + + /** + * Initializes a new instance of the TurnMemoryScope class. + * + * @param type The Type of T that is being created. + * @param name Name of the property. + */ + public BotStateMemoryScope(Class type, String name) { + super(name, true); + this.type = type; + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + T botState = getBotState(dialogContext); + if (botState != null) { + CachedBotState cachedState = botState.getCachedState(dialogContext.getContext()); + return cachedState.getState(); + } else { + return null; + } + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You cannot replace the root BotState Object."); + } + + /** + * + */ + @Override + public CompletableFuture load(DialogContext dialogContext, Boolean force) { + T botState = getBotState(dialogContext); + + if (botState != null) { + return botState.load(dialogContext.getContext(), force); + } else { + return CompletableFuture.completedFuture(null); + } + } + + /** + * @param dialogContext + * @param force + * @return A future that represents the + */ + @Override + public CompletableFuture saveChanges(DialogContext dialogContext, Boolean force) { + T botState = getBotState(dialogContext); + + if (botState != null) { + return botState.saveChanges(dialogContext.getContext(), force); + } else { + return CompletableFuture.completedFuture(null); + } + } + + private T getBotState(DialogContext dialogContext) { + return dialogContext.getContext().getTurnState().get(type); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java new file mode 100644 index 000000000..9df3664d2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class ClassMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public ClassMemoryScope() { + super(ScopePath.CLASS, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + // if active dialog is a container dialog then "dialog" binds to it. + if (dialogContext.getActiveDialog() != null) { + Dialog dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog != null) { + return new ReadOnlyObject(dialog); + } + } + return null; +} + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the class scope."); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java new file mode 100644 index 000000000..4c076515b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class ConversationMemoryScope extends BotStateMemoryScope { + /** + * DialogMemoryScope maps "this" -> dc.ActiveDialog.State. + */ + public ConversationMemoryScope() { + super(ConversationState.class, ScopePath.CONVERSATION); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java new file mode 100644 index 000000000..aee1efad4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.DialogContainer; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * Initializes a new instance of the DialogClassMemoryScope class. + */ +public class DialogClassMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the DialogClassMemoryScope class. + */ + public DialogClassMemoryScope() { + super(ScopePath.DIALOG_CLASS, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + // if active dialog is a container dialog then "dialogclass" binds to it. + if (dialogContext.getActiveDialog() != null) { + Object dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog instanceof DialogContainer) { + return new ReadOnlyObject(dialog); + } + } + + // Otherwise we always bind to parent, or if there is no parent the active + // dialog + if (dialogContext.getParent() != null && dialogContext.getParent().getActiveDialog() != null) { + return new ReadOnlyObject(dialogContext.findDialog(dialogContext.getParent().getActiveDialog().getId())); + } else if (dialogContext.getActiveDialog() != null) { + return new ReadOnlyObject(dialogContext.findDialog(dialogContext.getActiveDialog().getId())); + } else { + return null; + } + + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the dialogclass scope"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java new file mode 100644 index 000000000..ab2ddac4a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.Optional; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.ScopePath; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * DialogContextMemoryScope maps "dialogcontext" -> properties. + */ +public class DialogContextMemoryScope extends MemoryScope { + + private final String stackKey = "stack"; + + private final String activeDialogKey = "activeDialog"; + + private final String parentKey = "parent"; + + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public DialogContextMemoryScope() { + super(ScopePath.DIALOG_CONTEXT, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + JSONObject memory = new JSONObject(); + JSONArray stack = new JSONArray(); + DialogContext currentDc = dialogContext; + + // go to leaf node + while (currentDc.getChild() != null) { + currentDc = currentDc.getChild(); + } + + while (currentDc != null) { + // (PORTERS NOTE: javascript stack is reversed with top of stack on end) + currentDc.getStack().forEach(item -> { + if (item.getId().startsWith("ActionScope[")) { + stack.put(item.getId()); + } + + }); + + currentDc = currentDc.getParent(); + } + + // top of stack is stack[0]. + memory.put(stackKey, stack); + memory.put(activeDialogKey, Optional.ofNullable(dialogContext) + .map(DialogContext::getActiveDialog) + .map(DialogInstance::getId) + .orElse(null)); + memory.put(parentKey, Optional.ofNullable(dialogContext) + .map(DialogContext::getParent) + .map(DialogContext::getActiveDialog) + .map(DialogInstance::getId) + .orElse(null)); + return memory; + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the dialogcontext scope"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java new file mode 100644 index 000000000..24c1c0f28 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.Map; + +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContainer; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * DialogMemoryScope maps "dialog" -> dc.Parent?.ActiveDialog.State ?? ActiveDialog.State. + */ +public class DialogMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public DialogMemoryScope() { + super(ScopePath.DIALOG, true); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (dialogContext.getActiveDialog() != null) { + Dialog dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog instanceof DialogContainer) { + return dialogContext.getActiveDialog().getState(); + } + } + + if (dialogContext.getParent() != null) { + if (dialogContext.getParent().getActiveDialog() != null) { + return dialogContext.getParent().getActiveDialog().getState(); + } + } else if (dialogContext.getActiveDialog() != null) { + return dialogContext.getActiveDialog().getStackIndex(); + } + return null; + } + + /** + * Changes the backing object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (memory == null) { + throw new IllegalArgumentException("memory cannot be null."); + } + + if (!(memory instanceof Map)) { + throw new IllegalArgumentException("memory must be of type Map."); + } + + // if active dialog is a container dialog then "dialog" binds to it + if (dialogContext.getActiveDialog() != null) { + Dialog dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog instanceof DialogContainer && memory instanceof Map) { + dialogContext.getActiveDialog().getState().putAll((Map) memory); + return; + } + } else if (dialogContext.getParent().getActiveDialog() != null) { + dialogContext.getParent().getActiveDialog().getState().putAll((Map) memory); + return; + } + + throw new IllegalStateException( + "Cannot set DialogMemoryScope. There is no active dialog dialog or parent dialog in the context"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java new file mode 100644 index 000000000..eb3ede696 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.DialogContext; +import java.util.concurrent.CompletableFuture; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public abstract class MemoryScope { + /** + * Initializes a new instance of the class. + * + * @param name Name of the scope. + * @param includeInSnapshot Value indicating whether this memory should be included in snapshot. + */ + public MemoryScope(String name, Boolean includeInSnapshot) { + this.includeInSnapshot = includeInSnapshot; + this.name = name; + } + + /** + * Name of the scope. + */ + private String name; + + /** + * Value indicating whether this memory should be included in snapshot. + */ + private Boolean includeInSnapshot; + + /** + * @return String Gets the name of the scope. + */ + public String getName() { + return this.name; + } + + /** + * @param withName Sets the name of the scope. + */ + public void setName(String withName) { + this.name = withName; + } + + /** + * @return Boolean Returns the value indicating whether this memory should be included in snapshot. + */ + public Boolean getIncludeInSnapshot() { + return this.includeInSnapshot; + } + + + /** + * @param withIncludeInSnapshot Sets the value indicating whether this memory should be included in snapshot. + */ + public void setIncludeInSnapshot(Boolean withIncludeInSnapshot) { + this.includeInSnapshot = withIncludeInSnapshot; + } + + /** + * Get the backing memory for this scope. + * + * @param dialogContext The DialogContext to get from the memory store. + * @return Object The memory for this scope. + */ + public abstract Object getMemory(DialogContext dialogContext); + + /** + * Changes the backing object for the memory scope. + * + * @param dialogContext The DialogContext to set in memory store. + * @param memory The memory to set the DialogContext to. + */ + public abstract void setMemory(DialogContext dialogContext, Object memory); + + /** + * Populates the state cache for this from the storage layer. + * + * @param dialogContext The dialog context object for this turn. + * @param force True to overwrite any existing state cache or false to load state from storage only + * if the cache doesn't already exist. + * @return CompletableFuture A future that represents the work queued to execute. + */ + public CompletableFuture load(DialogContext dialogContext, Boolean force) { + return CompletableFuture.completedFuture(null); + } + + + /** + * Writes the state cache for this to the storage layer. + * + * @param dialogContext The dialog context Object for this turn. + * @param force True to save the state cache to storage. or false to save state to storage only + * if a property in the cache has changed. + * @return CompletableFuture A future that represents the work queued to execute. + */ + public CompletableFuture saveChanges(DialogContext dialogContext, Boolean force) { + return CompletableFuture.completedFuture(null); + } + + /** + * Deletes any state in storage and the cache for this. + * + * @param dialogContext The dialog context Object for this turn. + * @return CompletableFuture A future that represents the work queued to execute. + */ + public CompletableFuture delete(DialogContext dialogContext) { + return CompletableFuture.completedFuture(null); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java new file mode 100644 index 000000000..429ff88b3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; + +import com.microsoft.bot.dialogs.ObjectPath; + +/** + * ReadOnlyObject is a wrapper around any Object to prevent setting of + * properties on the Object. + */ +public class ReadOnlyObject extends Dictionary { + + private final String notSupported = "This Object is final."; + + private Object obj; + + /** + * + * @param obj Object to wrap. Any expression properties on it will be evaluated + * using the dc. + */ + public ReadOnlyObject(Object obj) { + this.obj = obj; + } + + /** + * @return The number of items. + */ + @Override + public int size() { + return ObjectPath.getProperties(obj).size(); + } + + /** + * @return The number of items. + */ + public int count() { + return size(); + } + + /** + * + */ + @Override + public boolean isEmpty() { + return false; + } + + /** + * + */ + @Override + public Enumeration keys() { + return Collections.enumeration(ObjectPath.getProperties(obj)); + } + + /** + * + */ + @Override + public Enumeration elements() { + Enumeration keys = this.keys(); + ArrayList elements = new ArrayList(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + elements.add(getValue(key)); + } + return Collections.enumeration(elements); + } + + /** + * + * @return The values. + */ + public Enumeration values() { + return elements(); + } + + /** + * + */ + @Override + public Object get(Object key) { + + if (!(key instanceof String)) { + throw new IllegalArgumentException("key is required and must be a String type."); + } + + return ObjectPath.tryGetPathValue(obj, (String) key, Object.class); + } + + /** + * + */ + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(notSupported); + } + + /** + * + */ + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(notSupported); + } + + /** + * Get a value based on a key. + * + * @param name Key of the value. + * @return The value associated with the provided key. + */ + public Object getValue(String name) { + Object value = ObjectPath.tryGetPathValue(obj, name, Object.class); + if (value != null) { + return new ReadOnlyObject(value); + } else { + return null; + } + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java new file mode 100644 index 000000000..81f05b6c4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.Properties; +import java.util.TreeMap; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; +import com.microsoft.bot.integration.Configuration; + +/** + * TurnMemoryScope represents memory scoped to the current turn. + */ +public class SettingsMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public SettingsMemoryScope() { + super(ScopePath.SETTINGS, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + Object returnValue; + + returnValue = dialogContext.getContext().getTurnState().get(ScopePath.TURN); + if (returnValue == null) { + Configuration configuration = dialogContext.getContext().getTurnState().get(Configuration.class); + if (configuration != null) { + returnValue = loadSettings(configuration); + dialogContext.getContext().getTurnState().add(ScopePath.SETTINGS, returnValue); + } + } + return returnValue; + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You cannot set the memory for a final memory scope"); + } + + /** + * Loads the settings from configuration. + * + * @param configuration The configuration to load Settings from. + * @return The collection of settings. + */ + protected static TreeMap loadSettings(Configuration configuration) { + TreeMap settings = new TreeMap(String.CASE_INSENSITIVE_ORDER); + + if (configuration != null) { + Properties properties = configuration.getProperties(); + properties.forEach((k, v) -> { + settings.put((String) k, v); + }); + } + + return settings; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java new file mode 100644 index 000000000..5eb375d4a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class ThisMemoryScope extends MemoryScope { + /** + * DialogMemoryScope maps "this" -> dc.ActiveDialog.State. + */ + public ThisMemoryScope() { + super(ScopePath.THIS, true); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (dialogContext.getActiveDialog() != null) { + return dialogContext.getActiveDialog().getState(); + } else { + return null; + } + + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the class scope."); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java new file mode 100644 index 000000000..2b96d9138 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.TreeMap; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * TurnMemoryScope represents memory scoped to the current turn. + */ +public class TurnMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public TurnMemoryScope() { + super(ScopePath.TURN, true); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + Object returnValue; + + returnValue = dialogContext.getContext().getTurnState().get(ScopePath.TURN); + if (returnValue == null) { + returnValue = new TreeMap(String.CASE_INSENSITIVE_ORDER); + dialogContext.getContext().getTurnState().add(ScopePath.TURN, returnValue); + } + + return returnValue; + } + + /** + * Changes the backing object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (dialogContext.getContext().getTurnState().containsKey(ScopePath.TURN)) { + dialogContext.getContext().getTurnState().replace(ScopePath.TURN, memory); + } else { + dialogContext.getContext().getTurnState().add(ScopePath.TURN, memory); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java new file mode 100644 index 000000000..b5ae8806c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class UserMemoryScope extends BotStateMemoryScope { + /** + * DialogMemoryScope maps "this" -> dc.ActiveDialog.State. + */ + public UserMemoryScope() { + super(UserState.class, ScopePath.USER); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java new file mode 100644 index 000000000..715f1bb43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.memory.scopes; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java new file mode 100644 index 000000000..33acff2b2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java new file mode 100644 index 000000000..6c30dae1a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.schema.Activity; +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogTurnResult; + +/** + * Defines the core behavior of a prompt dialog that waits for an activity to be + * received. + * + * This prompt requires a validator be passed in and is useful when waiting for + * non-message activities like an event to be received.The validator can ignore + * received activities until the expected activity type is received. + */ +public class ActivityPrompt extends Dialog { + + private final String persistedOptions = "options"; + private final String persistedState = "state"; + + private final PromptValidator validator; + + /** + * Initializes a new instance of the {@link ActivityPrompt} class. Called from + * constructors in derived classes to initialize the {@link ActivityPrompt} + * class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator A {@link PromptValidator{Activity}} that contains validation + * for this prompt. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which the + * prompt is added. + */ + public ActivityPrompt(String dialogId, PromptValidator validator) { + super(dialogId); + if (StringUtils.isEmpty(dialogId)) { + throw new IllegalArgumentException("dialogId cannot be empty"); + } + + if (validator == null) { + throw new IllegalArgumentException("validator cannot be null"); + } + + this.validator = validator; + } + + /** + * Called when a prompt dialog is pushed onto the dialog stack and is being activated. + * + * @param dc The dialog context for the current turn of the conversation. + * @param options Optional, additional information to pass to the prompt being started. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the prompt is still active after the + * turn has been processed by the prompt. + */ + + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (!(options instanceof PromptOptions)) { + return Async.completeExceptionally(new IllegalArgumentException( + "Prompt options are required for Prompt dialogs" + )); + } + + // Ensure prompts have input hint set + // For Java this code isn't necessary as InputHint is an enumeration, so it's can't be not set to something. + // PromptOptions opt = (PromptOptions) options; + // if (opt.getPrompt() != null && StringUtils.isBlank(opt.getPrompt().getInputHint().toString())) { + // opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); + // } + + // if (opt.getRetryPrompt() != null && StringUtils.isBlank(opt.getRetryPrompt().getInputHint().toString())) { + // opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); + // } + + // Initialize prompt state + Map state = dc.getActiveDialog().getState(); + state.put(persistedOptions, options); + + Map persistedStateMap = new HashMap(); + persistedStateMap.put(Prompt.ATTEMPTCOUNTKEY, 0); + state.put(persistedState, persistedStateMap); + + // Send initial prompt + onPrompt(dc.getContext(), (Map) state.get(persistedState), + (PromptOptions) state.get(persistedOptions), false); + + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog is the active dialog and the user replied with a + * new activity. + * + * @param dc The dialog context for the current turn of conversation. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * prompt generally continues to receive the user's replies until it + * accepts the user's reply as valid input for the prompt. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Perform base recognition + DialogInstance instance = dc.getActiveDialog(); + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); + + state.put(Prompt.ATTEMPTCOUNTKEY, (int) state.get(Prompt.ATTEMPTCOUNTKEY) + 1); + + // Validate the return value + boolean isValid = false; + if (validator != null) { + PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), + recognized, state, options); + isValid = validator.promptValidator(promptContext).join(); + } else if (recognized.getSucceeded()) { + isValid = true; + } + + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } + + onPrompt(dc.getContext(), state, options, true); + + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog resumes being the active dialog on the dialog + * stack, such as when the previous active dialog on the stack completes. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason An enum indicating why the dialog resumed. + * @param result Optional, value returned from the previous dialog on the stack. + * The type of the value returned is dependent on the previous + * dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + // Prompts are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the prompt receiving an unexpected + // call to + // dialogResume() when the pushed on dialog ends. + // To avoid the prompt prematurely ending we need to implement this method and + // simply re-prompt the user. + repromptDialog(dc.getContext(), dc.getActiveDialog()); + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog has been requested to re-prompt the user for + * input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param instance The instance of the dialog on the stack. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + onPrompt(turnContext, state, options, false); + return CompletableFuture.completedFuture(null); + } + + /** + * When overridden in a derived class, prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options) { + onPrompt(turnContext, state, options, false).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * When overridden in a derived class, prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry A {@link Boolean} representing if the prompt is a retry. + * + * @return A {@link CompletableFuture} representing the result of the asynchronous + * operation. + */ + protected CompletableFuture onPrompt( + TurnContext turnContext, + Map state, + PromptOptions options, + Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * When overridden in a derived class, attempts to recognize the incoming activity. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + PromptRecognizerResult result = new PromptRecognizerResult(); + result.setSucceeded(true); + result.setValue(turnContext.getActivity()); + + return CompletableFuture.completedFuture(result); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java new file mode 100644 index 000000000..13fc79dbb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; + +/** + * Prompts a user to upload attachments, like images. + */ +public class AttachmentPrompt extends Prompt> { + + /** + * Initializes a new instance of the {@link AttachmentPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public AttachmentPrompt(String dialogId) { + this(dialogId, null); + } + + /** + * Initializes a new instance of the {@link AttachmentPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{T}} that contains additional, + * custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public AttachmentPrompt(String dialogId, PromptValidator> validator) { + super(dialogId, validator); + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture>> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult> result = new PromptRecognizerResult>(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + Activity message = turnContext.getActivity(); + if (message.getAttachments() != null && message.getAttachments().size() > 0) { + result.setSucceeded(true); + result.setValue(message.getAttachments()); + } + } + + return CompletableFuture.completedFuture(result); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java deleted file mode 100644 index 53b32c613..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.microsoft.bot.dialogs.prompts; - -import com.microsoft.bot.schema.CardAction; - -import java.util.ArrayList; - -public class Choice -{ - /** - * Value to return when selected. - */ - String _value; - public void setValue(String value) { - this._value = value; - } - public String getValue() { - return this._value; - } - - /** - * (Optional) action to use when rendering the choice as a suggested action. - */ - CardAction _action; - public CardAction getAction() { - return this._action; - } - public void setAction(CardAction action) { - this._action = action; - } - - /** - * (Optional) list of synonyms to recognize in addition to the value. - */ - ArrayList _synonyms; - public ArrayList getSynonyms() { - return _synonyms; - } - public void setSynonyms(ArrayList synonyms) { - this._synonyms = synonyms; - } -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java new file mode 100644 index 000000000..533ce5f58 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ChoiceRecognizers; +import com.microsoft.bot.dialogs.choices.FindChoicesOptions; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.dialogs.choices.ModelResult; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.StringUtils; + +/** + * Prompts a user to select from a list of choices. + */ +public class ChoicePrompt extends Prompt { + + /** + * A dictionary of Default Choices based on {@link GetSupportedCultures} . Can + * be replaced by user using the constructor that contains choiceDefaults. + */ + private Map choiceDefaults; + + private ListStyle style; + private String defaultLocale; + private FindChoicesOptions recognizerOptions; + private ChoiceFactoryOptions choiceOptions; + + /** + * Initializes a new instance of the {@link ChoicePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not otherwise + * supported. + */ + public ChoicePrompt(String dialogId, PromptValidator validator, String defaultLocale) { + super(dialogId, validator); + + choiceDefaults = new HashMap(); + for (PromptCultureModel model : PromptCultureModels.getSupportedCultures()) { + choiceDefaults.put(model.getLocale(), new ChoiceFactoryOptions() { + { + setInlineSeparator(model.getSeparator()); + setInlineOr(model.getInlineOr()); + setInlineOrMore(model.getInlineOrMore()); + setIncludeNumbers(true); + } + } + ); + } + + this.style = ListStyle.AUTO; + this.defaultLocale = defaultLocale; + } + + /** + * Initializes a new instance of the {@link ChoicePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that contains + * additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is a 2, 3, or 4 character + * ISO 639 code + * that represents a language or language family. + * @param choiceDefaults Overrides the dictionary of Bot Framework SDK-supported + * _choiceDefaults (for prompt localization). Must be passed in to each ConfirmPrompt that + * needs the custom choice defaults. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. If the {@link Activity#locale} of the + * {@link DialogContext} .{@link DialogContext#context} .{@link ITurnContext#activity} is + * specified, then that local is used to determine language specific behavior; otherwise the + * {@link defaultLocale} is used. US-English is the used if no language or default locale is + * available, or if the language or locale is not otherwise supported. + */ + public ChoicePrompt(String dialogId, Map choiceDefaults, + PromptValidator validator, String defaultLocale) { + this(dialogId, validator, defaultLocale); + + this.choiceDefaults = choiceDefaults; + } + + /** + * Gets the style to use when presenting the prompt to the user. + * + * @return The style to use when presenting the prompt to the user. + */ + public ListStyle getStyle() { + return this.style; + } + + /** + * Sets the style to use when presenting the prompt to the user. + * + * @param style The style to use when presenting the prompt to the user. + */ + public void setStyle(ListStyle style) { + this.style = style; + } + + /** + * Sets or sets the default locale used to determine language-specific behavior + * of the prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Gets or sets additional options passed to the underlying + * {@link ChoiceRecognizers#recognizeChoices(String, IList{Choice}, + * FindChoicesOptions)} method. + * + * @return Options to control the recognition strategy. + */ + public FindChoicesOptions getRecognizerOptions() { + return this.recognizerOptions; + } + + /** + * Gets or sets additional options passed to the underlying + * {@link ChoiceRecognizers#recognizeChoices(String, IList{Choice}, + * FindChoicesOptions)} method. + * + * @param recognizerOptions Options to control the recognition strategy. + */ + public void setRecognizerOptions(FindChoicesOptions recognizerOptions) { + this.recognizerOptions = recognizerOptions; + } + + /** + * Gets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @return Additional options for presenting the set of choices. + */ + public ChoiceFactoryOptions getChoiceOptions() { + return this.choiceOptions; + } + + /** + * Sets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @param choiceOptions Additional options for presenting the set of choices. + */ + public void setChoiceOptions(ChoiceFactoryOptions choiceOptions) { + this.choiceOptions = choiceOptions; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + String culture = determineCulture(turnContext.getActivity()); + + // Format prompt to send + Activity prompt; + + List choices = options.getChoices() != null ? options.getChoices() : new ArrayList(); + String channelId = turnContext.getActivity().getChannelId(); + ChoiceFactoryOptions choiceOpts = getChoiceOptions() != null + ? getChoiceOptions() : choiceDefaults.get(culture); + ListStyle choiceStyle = options.getStyle() != null ? options.getStyle() : style; + + if (isRetry && options.getRetryPrompt() != null) { + prompt = appendChoices(options.getRetryPrompt(), channelId, choices, choiceStyle, choiceOpts); + } else { + prompt = appendChoices(options.getPrompt(), channelId, choices, choiceStyle, choiceOpts); + } + + // Send prompt + turnContext.sendActivity(prompt).join(); + + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + List choices = options.getChoices() != null ? options.getChoices() : new ArrayList(); + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + Activity activity = turnContext.getActivity(); + String utterance = activity.getText(); + if (StringUtils.isEmpty(utterance)) { + return CompletableFuture.completedFuture(result); + } + + FindChoicesOptions opt = recognizerOptions != null ? recognizerOptions : new FindChoicesOptions(); + opt.setLocale(determineCulture(activity, opt)); + List> results = ChoiceRecognizers.recognizeChoices(utterance, choices, opt); + if (results != null && results.size() > 0) { + result.setSucceeded(true); + result.setValue(results.get(0).getResolution()); + } + } + return CompletableFuture.completedFuture(result); + } + + private String determineCulture(Activity activity) { + return determineCulture(activity, null); + } + + private String determineCulture(Activity activity, FindChoicesOptions opt) { + + String locale; + if (activity.getLocale() != null) { + locale = activity.getLocale(); + } else if (opt != null) { + locale = opt.getLocale(); + } else if (defaultLocale != null) { + locale = defaultLocale; + } else { + locale = PromptCultureModels.ENGLISH_CULTURE; + } + + String culture = PromptCultureModels.mapToNearestLanguage(locale); + if (StringUtils.isBlank(culture) || !choiceDefaults.containsKey(culture)) { + culture = PromptCultureModels.ENGLISH_CULTURE; + } + return culture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java new file mode 100644 index 000000000..2a317d17a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -0,0 +1,353 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ChoiceRecognizers; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.dialogs.choices.ModelResult; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.choice.ChoiceRecognizer; + +import org.apache.commons.lang3.StringUtils; +import org.javatuples.Pair; +import org.javatuples.Triplet; + +/** + * Prompts a user to confirm something with a yes/no response. + */ +public class ConfirmPrompt extends Prompt { + + /** + * A dictionary of Default Choices based on {@link GetSupportedCultures} . Can + * be replaced by user using the constructor that contains choiceDefaults. + */ + private Map> choiceDefaults; + private ListStyle style; + private String defaultLocale; + private ChoiceFactoryOptions choiceOptions; + private Pair confirmChoices; + + /** + * Initializes a new instance of the {@link ConfirmPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + */ + public ConfirmPrompt(String dialogId) { + this(dialogId, null, null); + } + + /** + * Initializes a new instance of the {@link ConfirmPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not otherwise + * supported. + */ + public ConfirmPrompt(String dialogId, PromptValidator validator, String defaultLocale) { + super(dialogId, validator); + + choiceDefaults = new HashMap>(); + for (PromptCultureModel model : PromptCultureModels.getSupportedCultures()) { + Choice yesChoice = new Choice(model.getYesInLanguage()); + Choice noChoice = new Choice(model.getNoInLanguage()); + ChoiceFactoryOptions factoryOptions = new ChoiceFactoryOptions() { + { + setInlineSeparator(model.getSeparator()); + setInlineOr(model.getInlineOr()); + setInlineOrMore(model.getInlineOrMore()); + setIncludeNumbers(true); + } + }; + choiceDefaults.put(model.getLocale(), new Triplet(yesChoice, + noChoice, + factoryOptions)); + } + + this.style = ListStyle.AUTO; + this.defaultLocale = defaultLocale; + } + + /** + * Initializes a new instance of the {@link ConfirmPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * @param choiceDefaults Overrides the dictionary of Bot Framework SDK-supported + * _choiceDefaults (for prompt localization). Must be + * passed in to each ConfirmPrompt that needs the custom + * choice defaults. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} + * .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not + * otherwise supported. + */ + public ConfirmPrompt(String dialogId, Map> choiceDefaults, + PromptValidator validator, String defaultLocale) { + this(dialogId, validator, defaultLocale); + this.choiceDefaults = choiceDefaults; + } + + + /** + * Gets the style to use when presenting the prompt to the user. + * + * @return The style to use when presenting the prompt to the user. + */ + public ListStyle getStyle() { + return this.style; + } + + /** + * Sets the style to use when presenting the prompt to the user. + * + * @param style The style to use when presenting the prompt to the user. + */ + public void setStyle(ListStyle style) { + this.style = style; + } + + /** + * Sets or sets the default locale used to determine language-specific behavior + * of the prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Gets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @return Additional options for presenting the set of choices. + */ + public ChoiceFactoryOptions getChoiceOptions() { + return this.choiceOptions; + } + + /** + * Sets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @param choiceOptions Additional options for presenting the set of choices. + */ + public void setChoiceOptions(ChoiceFactoryOptions choiceOptions) { + this.choiceOptions = choiceOptions; + } + + /** + * Gets the yes and no {@link Choice} for the prompt. + * @return The yes and no {@link Choice} for the prompt. + */ + public Pair getConfirmChoices() { + return this.confirmChoices; + } + + /** + * Sets the yes and no {@link Choice} for the prompt. + * @param confirmChoices The yes and no {@link Choice} for the prompt. + */ + public void setConfirmChoices(Pair confirmChoices) { + this.confirmChoices = confirmChoices; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + // Format prompt to send + Activity prompt; + String channelId = turnContext.getActivity().getChannelId(); + String culture = determineCulture(turnContext.getActivity()); + Triplet defaults = choiceDefaults.get(culture); + ChoiceFactoryOptions localChoiceOptions = getChoiceOptions() != null ? getChoiceOptions() + : defaults.getValue2(); + List choices = new ArrayList(Arrays.asList(defaults.getValue0(), + defaults.getValue1())); + + ListStyle localStyle = options.getStyle() != null ? options.getStyle() : getStyle(); + if (isRetry && options.getRetryPrompt() != null) { + prompt = appendChoices(options.getRetryPrompt(), channelId, + choices, localStyle, localChoiceOptions); + } else { + prompt = appendChoices(options.getPrompt(), channelId, choices, + localStyle, localChoiceOptions); + } + + // Send prompt + turnContext.sendActivity(prompt).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + // Recognize utterance + String utterance = turnContext.getActivity().getText(); + if (StringUtils.isBlank(utterance)) { + return CompletableFuture.completedFuture(result); + } + + String culture = determineCulture(turnContext.getActivity()); + List results = + ChoiceRecognizer.recognizeBoolean(utterance, culture); + if (results.size() > 0) { + com.microsoft.recognizers.text.ModelResult first = results.get(0); + Boolean value = (Boolean) first.resolution.get("value"); + if (value != null) { + result.setSucceeded(true); + result.setValue(value); + } + } else { + // First check whether the prompt was sent to the user with numbers - + // if it was we should recognize numbers + Triplet defaults = choiceDefaults.get(culture); + ChoiceFactoryOptions choiceOpts = choiceOptions != null ? choiceOptions : defaults.getValue2(); + + // This logic reflects the fact that IncludeNumbers is nullable and True is the default + // set in Inline style + if (choiceOpts.getIncludeNumbers() == null + || choiceOpts.getIncludeNumbers() != null && choiceOpts.getIncludeNumbers()) { + // The text may be a number in which case we will interpret that as a choice. + Pair confirmedChoices = confirmChoices != null ? confirmChoices + : new Pair(defaults.getValue0(), defaults.getValue1()); + ArrayList choices = new ArrayList() { + { + add(confirmedChoices.getValue0()); + add(confirmedChoices.getValue1()); + } + }; + + List> secondAttemptResults = + ChoiceRecognizers.recognizeChoices(utterance, choices); + if (secondAttemptResults.size() > 0) { + result.setSucceeded(true); + result.setValue(secondAttemptResults.get(0).getResolution().getIndex() == 0); + } + } + } + } + + return CompletableFuture.completedFuture(result); + } + + private String determineCulture(Activity activity) { + + String locale; + if (activity.getLocale() != null) { + locale = activity.getLocale(); + } else if (defaultLocale != null) { + locale = defaultLocale; + } else { + locale = PromptCultureModels.ENGLISH_CULTURE; + } + + String culture = PromptCultureModels.mapToNearestLanguage(locale); + if (StringUtils.isBlank(culture) || !choiceDefaults.containsKey(culture)) { + culture = PromptCultureModels.ENGLISH_CULTURE; + } + return culture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java new file mode 100644 index 000000000..9e971c486 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.DateTimeRecognizer; + +import org.apache.commons.lang3.StringUtils; + +/** + * Prompts a user for a date-time value. + */ +public class DateTimePrompt extends Prompt> { + + private String defaultLocale; + + /** + * Initializes a new instance of the {@link DateTimePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator.FoundChoice} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not otherwise + * supported. + */ + public DateTimePrompt(String dialogId, PromptValidator> validator, String defaultLocale) { + super(dialogId, validator); + this.defaultLocale = defaultLocale; + } + + /** + * Gets the default locale used to determine language-specific behavior of the + * prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input as a date-time value. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture>> + onRecognize(TurnContext turnContext, Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult> result = + new PromptRecognizerResult>(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + String utterance = turnContext.getActivity().getText(); + if (StringUtils.isEmpty(utterance)) { + return CompletableFuture.completedFuture(result); + } + + String culture = turnContext.getActivity().getLocale() != null ? turnContext.getActivity().getLocale() + : defaultLocale != null ? defaultLocale : PromptCultureModels.ENGLISH_CULTURE; + LocalDateTime refTime = turnContext.getActivity().getLocalTimestamp() != null + ? turnContext.getActivity().getLocalTimestamp().toLocalDateTime() : null; + List results = + DateTimeRecognizer.recognizeDateTime(utterance, culture, DateTimeOptions.None, true, refTime); + if (results.size() > 0) { + // Return list of resolutions from first match + result.setSucceeded(true); + result.setValue(new ArrayList()); + List> values = (List>) results.get(0).resolution.get("values"); + for (Map mapEntry : values) { + result.getValue().add(readResolution(mapEntry)); + } + } + } + + return CompletableFuture.completedFuture(result); + } + + private static DateTimeResolution readResolution(Map resolution) { + DateTimeResolution result = new DateTimeResolution(); + + if (resolution.containsKey("timex")) { + result.setTimex(resolution.get("timex")); + } + + if (resolution.containsKey("value")) { + result.setValue(resolution.get("value")); + } + + if (resolution.containsKey("start")) { + result.setStart(resolution.get("start")); + } + + if (resolution.containsKey("end")) { + result.setEnd(resolution.get("end")); + } + return result; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java new file mode 100644 index 000000000..156904b32 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +/** + * A date-time value, as recognized by the {@link DateTimePrompt} . + * + * A value can represent a date, a time, a date and time, or a range of any of + * these. The representation of the value is determined by the locale used to + * parse the input. + */ + +public class DateTimeResolution { + + private String value; + private String start; + private String end; + private String timex; + + /** + * Gets a human-readable representation of the value, for a non-range result. + * + * @return A human-readable representation of the value, for a non-range result. + */ + public String getValue() { + return this.value; + } + + /** + * Sets a human-readable representation of the value, for a non-range result. + * + * @param value A human-readable representation of the value, for a non-range + * result. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Gets a human-readable representation of the start value, for a range result. + * + * @return A human-readable representation of the start value, for a range + * result. + */ + public String getStart() { + return this.start; + } + + /** + * Sets a human-readable representation of the start value, for a range result. + * + * @param start A human-readable representation of the start value, for a range + * result. + */ + public void setStart(String start) { + this.start = start; + } + + /** + * Gets a human-readable represntation of the end value, for a range result. + * + * @return A human-readable representation of the end value, for a range result. + */ + public String getEnd() { + return this.end; + } + + /** + * Sets a human-readable represntation of the end value, for a range result. + * + * @param end A human-readable representation of the end value, for a range + * result. + */ + public void setEnd(String end) { + this.end = end; + } + + /** + * Gets the value in TIMEX format. The TIMEX format that follows the ISO 8601 + * standard. + * + * @return A TIMEX representation of the value. + */ + public String getTimex() { + return this.timex; + } + + /** + * Sets the value in TIMEX format. The TIMEX format that follows the ISO 8601 + * standard. + * + * @param timex A TIMEX representation of the value. + */ + public void setTimex(String timex) { + this.timex = timex; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java new file mode 100644 index 000000000..2446328f7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java @@ -0,0 +1,252 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.number.NumberRecognizer; +import com.microsoft.recognizers.text.numberwithunit.NumberWithUnitRecognizer; + +import org.apache.commons.lang3.StringUtils; + +/** + * Prompts a user to enter a number. + * + * The number prompt currently supports these types: {@link float} , {@link int} + * , {@link long} , {@link double} , and {@link decimal} . + * @param numeric type for this prompt, which can be int, long, double, or float. + */ +public class NumberPrompt extends Prompt { + + private String defaultLocale; + private final Class classOfNumber; + + /** + * Initializes a new instance of the {@link NumberPrompt{T}} class. + * + * @param dialogId Unique ID of the dialog within its parent + * {@link DialogSet} or {@link ComponentDialog} . + * @param classOfNumber Type of used to determine within the class what type was created for. This is required + * due to type erasure in Java not allowing checking the type of during runtime. + * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for . + */ + public NumberPrompt(String dialogId, Class classOfNumber) + throws UnsupportedDataTypeException { + this(dialogId, null, null, classOfNumber); + } + + /** + * Initializes a new instance of the {@link NumberPrompt{T}} class. + * + * @param dialogId Unique ID of the dialog within its parent + * {@link DialogSet} or {@link ComponentDialog} . + * @param validator Validator that will be called each time the user + * responds to the prompt. + * @param defaultLocale Locale to use. + * @param classOfNumber Type of used to determine within the class what type was created for. This is required + * due to type erasure in Java not allowing checking the type of during runtime. + * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for . + */ + public NumberPrompt(String dialogId, PromptValidator validator, String defaultLocale, Class classOfNumber) + throws UnsupportedDataTypeException { + + super(dialogId, validator); + this.defaultLocale = defaultLocale; + this.classOfNumber = classOfNumber; + + if (!(classOfNumber.getSimpleName().equals("Long") || classOfNumber.getSimpleName().equals("Integer") + || classOfNumber.getSimpleName().equals("Float") || classOfNumber.getSimpleName().equals("Double"))) { + throw new UnsupportedDataTypeException(String.format("NumberPrompt: Type argument %s is not supported", + classOfNumber.getSimpleName())); + } + } + + /** + * Gets the default locale used to determine language-specific behavior of the + * prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance + * on the stack is prompting the user for input; otherwise, + * false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the + * recognition attempt. + */ + @Override + @SuppressWarnings("PMD") + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + String utterance = turnContext.getActivity().getText(); + if (StringUtils.isEmpty(utterance)) { + return CompletableFuture.completedFuture(result); + } + + String culture = turnContext.getActivity().getLocale() != null ? turnContext.getActivity().getLocale() + : defaultLocale != null ? defaultLocale : PromptCultureModels.ENGLISH_CULTURE; + List results = recognizeNumberWithUnit(utterance, culture); + if (results != null && results.size() > 0) { + // Try to parse value based on type + String text = ""; + + // Try to parse value based on type + Object valueResolution = results.get(0).resolution.get("value"); + if (valueResolution != null) { + text = (String) valueResolution; + } + + if (classOfNumber.getSimpleName().equals("Float")) { + try { + Float value = Float.parseFloat(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } else if (classOfNumber.getSimpleName().equals("Integer")) { + try { + Integer value = Integer.parseInt(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } else if (classOfNumber.getSimpleName().equals("Long")) { + try { + Long value = Long.parseLong(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } else if (classOfNumber.getSimpleName().equals("Double")) { + try { + Double value = Double.parseDouble(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } + } + } + return CompletableFuture.completedFuture(result); + } + + private static List recognizeNumberWithUnit(String utterance, String culture) { + List number = NumberRecognizer.recognizeNumber(utterance, culture); + + if (number.size() > 0) { + // Result when it matches with a number recognizer + return number; + } else { + List result; + // Analyze every option for numberWithUnit + result = NumberWithUnitRecognizer.recognizeCurrency(utterance, culture); + if (result.size() > 0) { + return result; + } + + result = NumberWithUnitRecognizer.recognizeAge(utterance, culture); + if (result.size() > 0) { + return result; + } + + result = NumberWithUnitRecognizer.recognizeTemperature(utterance, culture); + if (result.size() > 0) { + return result; + } + + result = NumberWithUnitRecognizer.recognizeDimension(utterance, culture); + if (result.size() > 0) { + return result; + } + + return null; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java new file mode 100644 index 000000000..b4130d560 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java @@ -0,0 +1,382 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogEvent; +import com.microsoft.bot.dialogs.DialogEvents; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.InputHints; + +import org.apache.commons.lang3.StringUtils; + +/** + * Defines the core behavior of prompt dialogs. + * + * When the prompt ends, it should return a Object that represents the value + * that was prompted for. Use {@link DialogSet#add(Dialog)} or + * {@link ComponentDialog#addDialog(Dialog)} to add a prompt to a dialog set or + * component dialog, respectively. Use + * {@link DialogContext#prompt(String, PromptOptions)} or + * {@link DialogContext#beginDialog(String, Object)} to start the prompt. If you + * start a prompt from a {@link WaterfallStep} in a {@link WaterfallDialog} , + * then the prompt result will be available in the next step of the waterfall. + * + * @param Type the prompt is created for. + */ +public abstract class Prompt extends Dialog { + + public static final String ATTEMPTCOUNTKEY = "AttemptCount"; + + private final String persistedOptions = "options"; + private final String persistedState = "state"; + private final PromptValidator validator; + + /** + * Initializes a new instance of the {@link Prompt{T}} class. Called from + * constructors in derived classes to initialize the {@link Prompt{T}} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{T}} that contains + * additional, custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which the + * prompt is added. + */ + public Prompt(String dialogId, PromptValidator validator) { + super(dialogId); + if (StringUtils.isBlank(dialogId)) { + throw new IllegalArgumentException("dialogId cannot be null"); + } + this.validator = validator; + } + + /** + * Called when a prompt dialog is pushed onto the dialog stack and is being + * activated. + * + * @param dc The dialog context for the current turn of the conversation. + * @param options Optional, additional information to pass to the prompt being + * started. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the prompt is + * still active after the turn has been processed by the prompt. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (!(options instanceof PromptOptions)) { + return Async.completeExceptionally(new IllegalArgumentException( + "Prompt options are required for Prompt dialogs" + )); + } + + // Ensure prompts have input hint set + PromptOptions opt = (PromptOptions) options; + + if (opt.getPrompt() != null && (opt.getPrompt().getInputHint() == null + || StringUtils.isEmpty(opt.getPrompt().getInputHint().toString()))) { + opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); + } + + if (opt.getRetryPrompt() != null && (opt.getRetryPrompt().getInputHint() == null + || StringUtils.isEmpty(opt.getRetryPrompt().getInputHint().toString()))) { + opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); + } + + + // Initialize prompt state + Map state = dc.getActiveDialog().getState(); + state.put(persistedOptions, opt); + + HashMap pState = new HashMap(); + pState.put(ATTEMPTCOUNTKEY, 0); + state.put(persistedState, pState); + + // Send initial prompt + onPrompt(dc.getContext(), + (Map) state.get(persistedState), + (PromptOptions) state.get(persistedOptions), + false); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + /** + * Called when a prompt dialog is the active dialog and the user replied with a + * new activity. + * + * @param dc The dialog context for the current turn of conversation. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * prompt generally continues to receive the user's replies until it + * accepts the user's reply as valid input for the prompt. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Don't do anything for non-message activities + if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) { + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + // Perform base recognition + DialogInstance instance = dc.getActiveDialog(); + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); + + state.put(ATTEMPTCOUNTKEY, (int) state.get(ATTEMPTCOUNTKEY) + 1); + + // Validate the return value + Boolean isValid = false; + if (validator != null) { + PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), + recognized, state, options); + isValid = validator.promptValidator(promptContext).join(); + } else if (recognized.getSucceeded()) { + isValid = true; + } + + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } + + if (!dc.getContext().getResponded()) { + onPrompt(dc.getContext(), state, options, true); + } + + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + /** + * Called when a prompt dialog resumes being the active dialog on the dialog + * stack, such as when the previous active dialog on the stack completes. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason An enum indicating why the dialog resumed. + * @param result Optional, value returned from the previous dialog on the stack. + * The type of the value returned is dependent on the previous + * dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + + // Prompts are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the prompt receiving an unexpected + // call to + // dialogResume() when the pushed on dialog ends. + // To avoid the prompt prematurely ending we need to implement this method and + // simply re-prompt the user. + repromptDialog(dc.getContext(), dc.getActiveDialog()).join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + /** + * Called when a prompt dialog has been requested to re-prompt the user for + * input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param instance The instance of the dialog on the stack. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + onPrompt(turnContext, state, options, false).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * Called before an event is bubbled to its parent. + * + * This is a good place to perform interception of an event as returning `true` will prevent + * any further bubbling of the event to the dialogs parents and will also prevent any child + * dialogs from performing their default processing. + * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * + * @return Whether the event is handled by the current dialog and further processing + * should stop. + */ + @Override + protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { + if (e.getName() == DialogEvents.ACTIVITY_RECEIVED + && dc.getContext().getActivity().getType() == ActivityTypes.MESSAGE) { + // Perform base recognition + Map state = dc.getActiveDialog().getState(); + PromptRecognizerResult recognized = onRecognize(dc.getContext(), + (Map) state.get(persistedState), (PromptOptions) state.get(persistedOptions)).join(); + return CompletableFuture.completedFuture(recognized.getSucceeded()); + } + + return CompletableFuture.completedFuture(false); + } + + /** + * When overridden in a derived class, prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance + * is on the stack is prompting the user for input; + * otherwise, false. Determines whether + * {@link PromptOptions#prompt} or + * {@link PromptOptions#retryPrompt} should be used. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + protected abstract CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry); + + /** + * When overridden in a derived class, attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the + * recognition attempt. + */ + protected abstract CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options); + + /** + * When overridden in a derived class, appends choices to the activity when the + * user is prompted for input. + * + * @param prompt The activity to append the choices to. + * @param channelId The ID of the user's channel. + * @param choices The choices to append. + * @param style Indicates how the choices should be presented to the user. + * @param options The formatting options to use when presenting the choices. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result contains the updated activity. + */ + protected Activity appendChoices(Activity prompt, String channelId, List choices, + ListStyle style, ChoiceFactoryOptions options) { + // Get base prompt text (if any) + String text = ""; + if (prompt != null && prompt.getText() != null && StringUtils.isNotBlank(prompt.getText())) { + text = prompt.getText(); + } + + // Create temporary msg + Activity msg; + switch (style) { + case INLINE: + msg = ChoiceFactory.inline(choices, text, null, options); + break; + + case LIST: + msg = ChoiceFactory.list(choices, text, null, options); + break; + + case SUGGESTED_ACTION: + msg = ChoiceFactory.suggestedAction(choices, text); + break; + + case HEROCARD: + msg = ChoiceFactory.heroCard(choices, text); + break; + + case NONE: + msg = Activity.createMessageActivity(); + msg.setText(text); + break; + + default: + msg = ChoiceFactory.forChannel(channelId, choices, text, null, options); + break; + } + + // Update prompt with text, actions and attachments + if (prompt != null) { + // clone the prompt the set in the options (note ActivityEx has Properties so this is the safest mechanism) + //prompt = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(prompt)); + prompt = Activity.clone(prompt); + + prompt.setText(msg.getText()); + + if (msg.getSuggestedActions() != null && msg.getSuggestedActions().getActions() != null + && msg.getSuggestedActions().getActions().size() > 0) { + prompt.setSuggestedActions(msg.getSuggestedActions()); + } + + if (msg.getAttachments() != null && msg.getAttachments().size() > 0) { + if (prompt.getAttachments() == null) { + prompt.setAttachments(msg.getAttachments()); + } else { + List allAttachments = prompt.getAttachments(); + prompt.getAttachments().addAll(msg.getAttachments()); + prompt.setAttachments(allAttachments); + } + } + + return prompt; + } + + msg.setInputHint(InputHints.EXPECTING_INPUT); + return msg; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java new file mode 100644 index 000000000..cbd369b8f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +/** + * Culture model used in Choice and Confirm Prompts. + */ +public class PromptCultureModel { + + private String locale; + private String separator; + private String inlineOr; + private String inlineOrMore; + private String yesInLanguage; + private String noInLanguage; + + /** + * Creates a PromptCultureModel. + */ + public PromptCultureModel() { + + } + + /** + * Gets Culture Model's Locale. + * + * @return Ex: Locale. Example: "en-US". + */ + public String getLocale() { + return this.locale; + } + + /** + * Sets Culture Model's Locale. + * + * @param locale Ex: Locale. Example: "en-US". + */ + public void setLocale(String locale) { + this.locale = locale; + } + + /** + * GetsCulture Model's Inline Separator. + * + * @return Example: ", ". + */ + public String getSeparator() { + return this.separator; + } + + /** + * Sets Culture Model's Inline Separator. + * + * @param separator Example: ", ". + */ + public void setSeparator(String separator) { + this.separator = separator; + } + + /** + * Gets Culture Model's InlineOr. + * + * @return Example: " or ". + */ + public String getInlineOr() { + return this.inlineOr; + } + + /** + * Sets Culture Model's InlineOr. + * + * @param inlineOr Example: " or ". + */ + public void setInlineOr(String inlineOr) { + this.inlineOr = inlineOr; + } + + /** + * Gets Culture Model's InlineOrMore. + * + * @return Example: ", or ". + */ + public String getInlineOrMore() { + return this.inlineOrMore; + } + + /** + * Sets Culture Model's InlineOrMore. + * + * @param inlineOrMore Example: ", or ". + */ + public void setInlineOrMore(String inlineOrMore) { + this.inlineOrMore = inlineOrMore; + } + + /** + * Gets Equivalent of "Yes" in Culture Model's Language. + * + * @return Example: "Yes". + */ + public String getYesInLanguage() { + return this.yesInLanguage; + } + + /** + * Sets Equivalent of "Yes" in Culture Model's Language. + * + * @param yesInLanguage Example: "Yes". + */ + public void setYesInLanguage(String yesInLanguage) { + this.yesInLanguage = yesInLanguage; + } + + /** + * Gets Equivalent of "No" in Culture Model's Language. + * + * @return Example: "No". + */ + public String getNoInLanguage() { + return this.noInLanguage; + } + + /** + * Sets Equivalent of "No" in Culture Model's Language. + * + * @param noInLanguage Example: "No". + */ + public void setNoInLanguage(String noInLanguage) { + this.noInLanguage = noInLanguage; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java new file mode 100644 index 000000000..edb733220 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java @@ -0,0 +1,308 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Class container for currently-supported Culture Models in Confirm and Choice + * Prompt. + */ +public final class PromptCultureModels { + + private PromptCultureModels() { + + } + + public static final String BULGARIAN_CULTURE = "bg-bg"; + public static final String CHINESE_CULTURE = "zh-cn"; + public static final String DUTCH_CULTURE = "nl-nl"; + public static final String ENGLISH_CULTURE = "en-us"; + public static final String FRENCH_CULTURE = "fr-fr"; + public static final String GERMAN_CULTURE = "de-de"; + public static final String HINDI_CULTURE = "hi-in"; + public static final String ITALIAN_CULTURE = "it-it"; + public static final String JAPANESE_CULTURE = "ja-jp"; + public static final String KOREAN_CULTURE = "ko-kr"; + public static final String PORTUGUESE_CULTURE = "pt-br"; + public static final String SPANISH_CULTURE = "es-es"; + public static final String SWEDISH_CULTURE = "sv-se"; + public static final String TURKISH_CULTURE = "tr-tr"; + + /** + * Gets the bulgarian prompt culture model. + */ + public static final PromptCultureModel BULGARIAN = new PromptCultureModel() { + { + setInlineOr(" или "); + setInlineOrMore(", или "); + setLocale(BULGARIAN_CULTURE); + setNoInLanguage("Не"); + setSeparator(", "); + setYesInLanguage("да"); + } + }; + + public static final PromptCultureModel CHINESE = new PromptCultureModel() { + { + setInlineOr(" 要么 "); + setInlineOrMore(", 要么 "); + setLocale(CHINESE_CULTURE); + setNoInLanguage("不"); + setSeparator(", "); + setYesInLanguage("是的"); + } + }; + + /** + * Gets the dutch prompt culture model. + */ + public static final PromptCultureModel DUTCH = new PromptCultureModel() { + { + setInlineOr(" of "); + setInlineOrMore(", of "); + setLocale(DUTCH_CULTURE); + setNoInLanguage("Nee"); + setSeparator(", "); + setYesInLanguage("Ja"); + } + }; + + /** + * Gets the english prompt culture model. + */ + public static final PromptCultureModel ENGLISH = new PromptCultureModel() { + { + setInlineOr(" or "); + setInlineOrMore(", or "); + setLocale(ENGLISH_CULTURE); + setNoInLanguage("No"); + setSeparator(", "); + setYesInLanguage("Yes"); + } + }; + + /** + * Gets the french prompt culture model. + */ + public static final PromptCultureModel FRENCH = new PromptCultureModel() { + { + setInlineOr(" ou "); + setInlineOrMore(", ou "); + setLocale(FRENCH_CULTURE); + setNoInLanguage("Non"); + setSeparator(", "); + setYesInLanguage("Oui"); + } + }; + + /** + * Gets the german prompt culture model. + */ + public static final PromptCultureModel GERMAN = new PromptCultureModel() { + { + setInlineOr(" oder "); + setInlineOrMore(", oder "); + setLocale(GERMAN_CULTURE); + setNoInLanguage("Nein"); + setSeparator(", "); + setYesInLanguage("Ja"); + } + }; + + /** + * Gets the hindi prompt culture model. + */ + public static final PromptCultureModel HINDI = new PromptCultureModel() { + { + setInlineOr(" या "); + setInlineOrMore(", या "); + setLocale(HINDI_CULTURE); + setNoInLanguage("नहीं"); + setSeparator(", "); + setYesInLanguage("हां"); + } + }; + + /** + * Gets the italian prompt culture model. + */ + public static final PromptCultureModel ITALIAN = new PromptCultureModel() { + { + setInlineOr(" o "); + setInlineOrMore(" o "); + setLocale(ITALIAN_CULTURE); + setNoInLanguage("No"); + setSeparator(", "); + setYesInLanguage("Si"); + } + }; + + /** + * Gets the japanese prompt culture model. + */ + public static final PromptCultureModel JAPANESE = new PromptCultureModel() { + { + setInlineOr(" または "); + setInlineOrMore("、 または "); + setLocale(JAPANESE_CULTURE); + setNoInLanguage("いいえ"); + setSeparator("、 "); + setYesInLanguage("はい"); + } + }; + + /** + * Gets the korean prompt culture model. + */ + public static final PromptCultureModel KOREAN = new PromptCultureModel() { + { + setInlineOr(" 또는 "); + setInlineOrMore(" 또는 "); + setLocale(KOREAN_CULTURE); + setNoInLanguage("아니"); + setSeparator(", "); + setYesInLanguage("예"); + } + }; + + /** + * Gets the portuguese prompt culture model. + */ + public static final PromptCultureModel PORTUGUESE = new PromptCultureModel() { + { + setInlineOr(" ou "); + setInlineOrMore(", ou "); + setLocale(PORTUGUESE_CULTURE); + setNoInLanguage("Não"); + setSeparator(", "); + setYesInLanguage("Sim"); + } + }; + + /** + * Gets the spanish prompt culture model. + */ + public static final PromptCultureModel SPANISH = new PromptCultureModel() { + { + setInlineOr(" o "); + setInlineOrMore(", o "); + setLocale(SPANISH_CULTURE); + setNoInLanguage("No"); + setSeparator(", "); + setYesInLanguage("Sí"); + } + }; + + /** + * Gets the swedish prompt culture model. + */ + public static final PromptCultureModel SWEDISH = new PromptCultureModel() { + { + setInlineOr(" eller "); + setInlineOrMore(" eller "); + setLocale(SWEDISH_CULTURE); + setNoInLanguage("Nej"); + setSeparator(", "); + setYesInLanguage("Ja"); + } + }; + + /** + * Gets the turkish prompt culture model. + */ + public static final PromptCultureModel TURKISH = new PromptCultureModel() { + { + setInlineOr(" veya "); + setInlineOrMore(" veya "); + setLocale(TURKISH_CULTURE); + setNoInLanguage("Hayır"); + setSeparator(", "); + setYesInLanguage("Evet"); + } + }; + + private static PromptCultureModel[] promptCultureModelArray = + { + BULGARIAN, + CHINESE, + DUTCH, + ENGLISH, + FRENCH, + GERMAN, + HINDI, + ITALIAN, + JAPANESE, + KOREAN, + PORTUGUESE, + SPANISH, + SWEDISH, + TURKISH + }; + + /** + * Gets a list of the supported culture models. + * + * @return Array of {@link PromptCultureModel} with the supported cultures. + */ + public static PromptCultureModel[] getSupportedCultures() { + return promptCultureModelArray; + } + + private static final List SUPPORTED_LOCALES = Arrays.stream(getSupportedCultures()) + .map(x -> x.getLocale()).collect(Collectors.toList()); + + // private static List supportedlocales; + + // static { + // supportedlocales = new ArrayList(); + // PromptCultureModel[] cultures = getSupportedCultures(); + // for (PromptCultureModel promptCultureModel : cultures) { + // supportedlocales.add(promptCultureModel.getLocale()); + // } + // } + + /** + * Use Recognizers-Text to normalize various potential setLocale strings to a standard. + * + * This is mostly a copy/paste from + * https://github.com/microsoft/Recognizers-Text/blob/master/.NET/Microsoft.Recognizers.Text/C + * lture.cs#L66 This doesn't directly use Recognizers-Text's MapToNearestLanguage because if + * they add language support before we do, it will break our prompts. + * + * @param cultureCode Represents setLocale. Examples: "en-US, en-us, EN". + * + * @return Normalized setLocale. + */ + public static String mapToNearestLanguage(String cultureCode) { + cultureCode = cultureCode.toLowerCase(); + final String cCode = cultureCode; + + if (SUPPORTED_LOCALES.stream().allMatch(o -> o != cCode)) { + // Handle cases like EnglishOthers with cultureCode "en-*" + List fallbackCultureCodes = SUPPORTED_LOCALES.stream() + .filter(o -> o.endsWith("*") + && cCode.startsWith(o.split("-")[0])) + .collect(Collectors.toList()); + + if (fallbackCultureCodes.size() == 1) { + return fallbackCultureCodes.get(0); + } + + //If there is no cultureCode like "-*", map only the prefix + //For example, "es-mx" will be mapped to "es-es" + fallbackCultureCodes = SUPPORTED_LOCALES.stream() + .filter(o -> cCode.startsWith(o.split("-")[0])) + .collect(Collectors.toList()); + + if (fallbackCultureCodes.size() > 0) { + return fallbackCultureCodes.get(0); + } + } + + return cultureCode; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java new file mode 100644 index 000000000..33ec403e1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import java.util.List; + + +/** + * Contains settings to pass to a {@link com.} when the prompt is started. + */ +public class PromptOptions { + + private Activity prompt; + + private Activity retryPrompt; + + private List choices; + + private ListStyle style; + + private Object validations; + + + /** + * @return Activity + */ + public Activity getPrompt() { + return this.prompt; + } + + + /** + * @param withPrompt value to set the Prompt property to + */ + public void setPrompt(Activity withPrompt) { + this.prompt = withPrompt; + } + + + /** + * @return Activity + */ + public Activity getRetryPrompt() { + return this.retryPrompt; + } + + + /** + * @param withRetryPrompt value to set the Retry property to + */ + public void setRetryPrompt(Activity withRetryPrompt) { + this.retryPrompt = withRetryPrompt; + } + + + /** + * @return List + */ + public List getChoices() { + return this.choices; + } + + + /** + * @param withChoices value to set the Choices property to + */ + public void setChoices(List withChoices) { + this.choices = withChoices; + } + + + /** + * @return ListStyle + */ + public ListStyle getStyle() { + return this.style; + } + + + /** + * @param withStyle value to set the Style property to + */ + public void setStyle(ListStyle withStyle) { + this.style = withStyle; + } + + + /** + * @return Object + */ + public Object getValidations() { + return this.validations; + } + + + /** + * @param withValidations value to set the Validations property to + */ + public void setValidations(Object withValidations) { + this.validations = withValidations; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java new file mode 100644 index 000000000..ea9c6391c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +/** + * Contains the result returned by the recognition method of a {@link Prompt{T}} + * . + * + * @param The type of value the prompt returns. + */ +public class PromptRecognizerResult { + + private T value; + private Boolean succeeded; + private Boolean allowInterruption = false; + + /** + * Initializes a new instance of the {@link PromptRecognizerResult{T}} class. + */ + public PromptRecognizerResult() { + succeeded = false; + } + + /** + * Gets the recognition value. + * + * @return The recognition value. + */ + public T getValue() { + return this.value; + } + + /** + * Sets the recogntion value. + * + * @param value Value to set the recognition value to. + */ + public void setValue(T value) { + this.value = value; + } + + /** + * Gets a value indicating whether the recognition attempt succeeded. + * + * @return True if the recognition attempt succeeded; otherwise, false. + */ + public Boolean getSucceeded() { + return this.succeeded; + } + + /** + * Sets a value indicating whether the recognition attempt succeeded. + * + * @param succeeded True if the recognition attempt succeeded; otherwise, false. + */ + + public void setSucceeded(Boolean succeeded) { + this.succeeded = succeeded; + } + + /** + * Gets a value indicating whether flag indicating whether or not parent dialogs + * should be allowed to interrupt the prompt. + * + * @return The default value is `false`. + */ + public Boolean getAllowInterruption() { + return this.allowInterruption; + } + + /** + * Sets a value indicating whether flag indicating whether or not parent dialogs + * should be allowed to interrupt the prompt. + * + * @param allowInterruption The default value is `false`. + */ + public void setAllowInterruption(Boolean allowInterruption) { + this.allowInterruption = allowInterruption; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java new file mode 100644 index 000000000..8fe211cbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +/** + * The interface definition for custom prompt validators. Implement this + * function to add custom validation to a prompt. + * + * @param Type the PromptValidator is created for. + */ + +public interface PromptValidator { + + /** + * The delegate definition for custom prompt validators. Implement this function to add custom + * validation to a prompt. + * + * @param promptContext The prompt validation context. + * + * @return A {@link CompletableFuture} of bool representing the asynchronous operation + * indicating validation success or failure. + */ + CompletableFuture promptValidator(PromptValidatorContext promptContext); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java new file mode 100644 index 000000000..57b95abb4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.Map; + +import com.microsoft.bot.builder.TurnContext; + +/** + * Contains context information for a {@link PromptValidator{T}} . + * + * @param Type for this Context + */ + +public class PromptValidatorContext { + + private Map state; + private PromptOptions options; + private TurnContext context; + private PromptRecognizerResult recognized; + + /** + * Create a PromptValidatorContext Instance. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param recognized The recognition results from the prompt's recognition + * attempt. + * @param state State for the associated prompt instance. + * @param options The prompt options used for this recognition attempt. + */ + public PromptValidatorContext(TurnContext turnContext, PromptRecognizerResult recognized, + Map state, PromptOptions options) { + this.context = turnContext; + this.options = options; + this.recognized = recognized; + this.state = state; + } + + /** + * Gets state for the associated prompt instance. + * + * @return State for the associated prompt instance. + */ + public Map getState() { + return this.state; + } + + /** + * Gets the {@link PromptOptions} used for this recognition attempt. + * + * @return The prompt options used for this recognition attempt. + */ + public PromptOptions getOptions() { + return this.options; + } + + /** + * Gets the {@link TurnContext} for the current turn of conversation with the + * user. + * + * @return Context for the current turn of conversation with the user. + */ + public TurnContext getContext() { + return this.context; + } + + /** + * Gets the {@link PromptRecognizerResult{T}} returned from the prompt's + * recognition attempt. + * + * @return The recognition results from the prompt's recognition attempt. + */ + public PromptRecognizerResult getRecognized() { + return this.recognized; + } + + /** + * Gets the number of times this instance of the prompt has been executed. + * + * This count is set when the prompt is added to the dialog stack. + * + * @return the attempt count. + */ + public int getAttemptCount() { + if (!state.containsKey(Prompt.ATTEMPTCOUNTKEY)) { + return 0; + } + + return (int) state.get(Prompt.ATTEMPTCOUNTKEY); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java new file mode 100644 index 000000000..a26c9b2e5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogEvent; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +/** + * Prompts the user for text input. + */ +public class TextPrompt extends Prompt { + + /** + * Initializes a new instance of the {@link TextPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public TextPrompt(String dialogId) { + this(dialogId, null); + } + + /** + * Initializes a new instance of the {@link TextPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that contains + * additional, custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public TextPrompt(String dialogId, PromptValidator validator) { + super(dialogId, validator); + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + Activity message = turnContext.getActivity(); + if (message.getText() != null) { + result.setSucceeded(true); + result.setValue(message.getText()); + } + } + + return CompletableFuture.completedFuture(result); + } + + /** + * Called before an event is bubbled to its parent. + * + * This is a good place to perform interception of an event as returning `true` will prevent + * any further bubbling of the event to the dialogs parents and will also prevent any child + * dialogs from performing their default processing. + * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * + * @return Whether the event is handled by the current dialog and further processing + * should stop. + */ + @Override + protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { + return CompletableFuture.completedFuture(false); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java new file mode 100644 index 000000000..9db4e7b18 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.prompts; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java new file mode 100644 index 000000000..d5e0a77a4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text; + +import java.util.Arrays; + +public class Culture { + public static final String English = "en-us"; + public static final String Chinese = "zh-cn"; + public static final String Spanish = "es-es"; + public static final String Portuguese = "pt-br"; + public static final String French = "fr-fr"; + public static final String German = "de-de"; + public static final String Japanese = "ja-jp"; + public static final String Dutch = "nl-nl"; + public static final String Italian = "it-it"; + + public static final Culture[] SupportedCultures = new Culture[]{ + new Culture("English", English), + new Culture("Chinese", Chinese), + new Culture("Spanish", Spanish), + new Culture("Portuguese", Portuguese), + new Culture("French", French), + new Culture("German", German), + new Culture("Japanese", Japanese), + new Culture("Dutch", Dutch), + new Culture("Italian", Italian), + }; + + public final String cultureName; + public final String cultureCode; + + public Culture(String cultureName, String cultureCode) { + this.cultureName = cultureName; + this.cultureCode = cultureCode; + } + + public static String[] getSupportedCultureCodes() { + return Arrays.stream(SupportedCultures) + .map(c -> c.cultureCode) + .toArray(String[]::new); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java new file mode 100644 index 000000000..fb3bd8949 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text; + +public class CultureInfo { + + public final String cultureCode; + + public CultureInfo(String cultureCode) { + this.cultureCode = cultureCode; + } + + public static CultureInfo getCultureInfo(String cultureCode) { + return new CultureInfo(cultureCode); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java new file mode 100644 index 000000000..bc8931903 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text; + +import java.util.SortedMap; + +public class ExtendedModelResult extends ModelResult { + // Parameter Key + public static final String ParentTextKey = "parentText"; + + public final String parentText; + + public ExtendedModelResult(String text, int start, int end, String typeName, SortedMap resolution, String parentText) { + super(text, start, end, typeName, resolution); + this.parentText = parentText; + } + + public ExtendedModelResult(ModelResult result, String parentText) { + this(result.text, result.start, result.end, result.typeName, result.resolution, parentText); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java new file mode 100644 index 000000000..4cdae8445 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java @@ -0,0 +1,103 @@ +package com.microsoft.recognizers.text; + +public class ExtractResult { + + private Integer start; + private Integer length; + private Object data; + private String type; + private String text; + private Metadata metadata; + + public ExtractResult() { + this(null, null, null, null); + } + + public ExtractResult(Integer start, Integer length, String text, String type) { + this(start, length, text, type, null, null); + } + + public ExtractResult(Integer start, Integer length, String text, String type, Object data, Metadata metadata) { + this.start = start; + this.length = length; + this.text = text; + this.type = type; + this.data = data; + this.metadata = metadata; + } + + public ExtractResult(Integer start, Integer length, String text, String type, Object data) { + this.start = start; + this.length = length; + this.text = text; + this.type = type; + this.data = data; + this.metadata = null; + } + + private boolean isOverlap(ExtractResult er1, ExtractResult er2) { + return !(er1.getStart() >= er2.getStart() + er2.getLength()) && + !(er2.getStart() >= er1.getStart() + er1.getLength()); + } + + public boolean isOverlap(ExtractResult er) { + return isOverlap(this, er); + } + + private boolean isCover(ExtractResult er1, ExtractResult er2) { + return ((er2.getStart() < er1.getStart()) && ((er2.getStart() + er2.getLength()) >= (er1.getStart() + er1.getLength()))) || + ((er2.getStart() <= er1.getStart()) && ((er2.getStart() + er2.getLength()) > (er1.getStart() + er1.getLength()))); + } + + public boolean isCover(ExtractResult er) { + return isCover(this, er); + } + + public Integer getStart() { + return start; + } + + public void setStart(Integer start) { + this.start = start; + } + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java new file mode 100644 index 000000000..35a5e1bea --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text; + +import java.util.List; + +public interface IExtractor { + List extract(String input); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java new file mode 100644 index 000000000..cc529689a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text; + +import java.util.List; + +public interface IModel { + String getModelTypeName(); + + List parse(String query); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java new file mode 100644 index 000000000..45c7ce76a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java @@ -0,0 +1,5 @@ +package com.microsoft.recognizers.text; + +public interface IParser { + ParseResult parse(ExtractResult extractResult); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java new file mode 100644 index 000000000..9e89af535 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java @@ -0,0 +1,49 @@ +package com.microsoft.recognizers.text; + +public class Metadata { + // For cases like "from 2014 to 2018", the period end "2018" could be inclusive or exclusive + // For extraction, we only mark this flag to avoid future duplicate judgment, whether to include the period end or not is not determined in the extraction step + private boolean possiblyIncludePeriodEnd = false; + + // For cases like "2015年以前" (usually regards as "before 2015" in English), "5天以前" + // (usually regards as "5 days ago" in English) in Chinese, we need to decide whether this is a "Date with Mode" or "Duration with Before and After". + // We use this flag to avoid duplicate judgment both in the Extraction step and Parse step. + // Currently, this flag is only used in Chinese DateTime as other languages don't have this ambiguity cases. + private boolean isDurationWithBeforeAndAfter = false; + + private boolean isHoliday = false; + + public boolean getIsHoliday() { + return isHoliday; + } + + public void setIsHoliday(boolean isHoliday) { + this.isHoliday = isHoliday; + } + + private boolean hasMod = false; + + public boolean getHasMod() { + return hasMod; + } + + public void setHasMod(boolean hasMod) { + this.hasMod = hasMod; + } + + public boolean getIsPossiblyIncludePeriodEnd() { + return possiblyIncludePeriodEnd; + } + + public void setPossiblyIncludePeriodEnd(boolean possiblyIncludePeriodEnd) { + this.possiblyIncludePeriodEnd = possiblyIncludePeriodEnd; + } + + public boolean getIsDurationWithBeforeAndAfter() { + return isDurationWithBeforeAndAfter; + } + + public void setDurationWithBeforeAndAfter(boolean durationWithBeforeAndAfter) { + isDurationWithBeforeAndAfter = durationWithBeforeAndAfter; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java new file mode 100644 index 000000000..ef2ec6370 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java @@ -0,0 +1,81 @@ +package com.microsoft.recognizers.text; + +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import org.javatuples.Pair; +import org.javatuples.Triplet; + +public class ModelFactory extends HashMap, Function> { + + // cacheKey: (string culture, Type modelType, string modelOptions) + private static ConcurrentHashMap, IModel> cache = new ConcurrentHashMap, IModel>(); + + private static final String fallbackCulture = Culture.English; + + public T getModel(Class modelType, String culture, boolean fallbackToDefaultCulture, TModelOptions options) throws IllegalArgumentException { + IModel model = this.getModel(modelType, culture, options); + if (model != null) { + return (T)model; + } + + if (fallbackToDefaultCulture) { + model = this.getModel(modelType, fallbackCulture, options); + if (model != null) { + return (T)model; + } + } + + throw new IllegalArgumentException( + String.format("Could not find Model with the specified configuration: %s, %s", culture, modelType.getTypeName())); + } + + private IModel getModel(Type modelType, String culture, TModelOptions options) { + if (StringUtility.isNullOrEmpty(culture)) { + return null; + } + + // Look in cache + Triplet cacheKey = new Triplet<>(culture.toLowerCase(), modelType, options.toString()); + if (cache.containsKey(cacheKey)) { + return cache.get(cacheKey); + } + + // Use Factory to create instance + Pair key = generateKey(culture, modelType); + if (this.containsKey(key)) { + Function factoryMethod = this.get(key); + IModel model = factoryMethod.apply(options); + + // Store in cache + cache.put(cacheKey, model); + return model; + } + + return null; + } + + public void initializeModels(String targetCulture, TModelOptions options) { + this.keySet().stream() + .filter(key -> StringUtility.isNullOrEmpty(targetCulture) || key.getValue0().equalsIgnoreCase(targetCulture)) + .forEach(key -> this.initializeModel(key.getValue1(), key.getValue0(), options)); + } + + private void initializeModel(Type modelType, String culture, TModelOptions options) { + this.getModel(modelType, culture, options); + } + + @Override + public Function put(Pair config, Function modelCreator) { + return super.put( + generateKey(config.getValue0(), config.getValue1()), + modelCreator); + } + + private static Pair generateKey(String culture, Type modelType) { + return new Pair<>(culture.toLowerCase(), modelType); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java new file mode 100644 index 000000000..69229e121 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java @@ -0,0 +1,20 @@ +package com.microsoft.recognizers.text; + +import java.util.SortedMap; + +public class ModelResult { + + public final String text; + public final int start; + public final int end; + public final String typeName; + public final SortedMap resolution; + + public ModelResult(String text, int start, int end, String typeName, SortedMap resolution) { + this.text = text; + this.start = start; + this.end = end; + this.typeName = typeName; + this.resolution = resolution; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java new file mode 100644 index 000000000..9a7b0a8cb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java @@ -0,0 +1,46 @@ +package com.microsoft.recognizers.text; + +public class ParseResult extends ExtractResult { + + // Value is for resolution. + // e.g. 1000 for "one thousand". + // The resolutions are different for different parsers. + // Therefore, we use object here. + private Object value; + + // Output the value in string format. + // It is used in some parsers. + private String resolutionStr; + + public ParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr) { + super(start, length, text, type, data, null); + this.value = value; + this.resolutionStr = resolutionStr; + } + + public ParseResult(ExtractResult er) { + this(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), null, null); + } + + public ParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr, Metadata metadata) { + super(start, length, text, type, data, metadata); + this.value = value; + this.resolutionStr = resolutionStr; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getResolutionStr() { + return resolutionStr; + } + + public void setResolutionStr(String resolutionStr) { + this.resolutionStr = resolutionStr; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java new file mode 100644 index 000000000..e0d2253f8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text; + +import java.util.function.Function; +import org.javatuples.Pair; + +public abstract class Recognizer> { + + public final String targetCulture; + public final TRecognizerOptions options; + + private final ModelFactory factory; + + protected Recognizer(String targetCulture, TRecognizerOptions options, boolean lazyInitialization) { + this.targetCulture = targetCulture; + this.options = options; + + this.factory = new ModelFactory<>(); + this.initializeConfiguration(); + + if (!lazyInitialization) { + this.initializeModels(targetCulture, options); + } + } + + public T getModel(Class modelType, String culture, boolean fallbackToDefaultCulture) { + return this.factory.getModel( + modelType, + culture != null ? culture : targetCulture, + fallbackToDefaultCulture, + options); + } + + public void registerModel(Class modelType, String culture, Function modelCreator) { + this.factory.put(new Pair<>(culture, modelType), modelCreator); + } + + private void initializeModels(String targetCulture, TRecognizerOptions options) { + this.factory.initializeModels(targetCulture, options); + } + + protected abstract void initializeConfiguration(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java new file mode 100644 index 000000000..6e14a7ee5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text; + +public class ResolutionKey { + public static final String ValueSet = "values"; + public static final String Value = "value"; + public static final String Type = "type"; + public static final String Unit = "unit"; + public static final String Score = "score"; + public static final String IsoCurrency = "isoCurrency"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java new file mode 100644 index 000000000..16470e707 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java @@ -0,0 +1,5 @@ +package com.microsoft.recognizers.text.choice; + +public enum ChoiceOptions { + None +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java new file mode 100644 index 000000000..782046b0f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java @@ -0,0 +1,74 @@ +package com.microsoft.recognizers.text.choice; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.choice.english.extractors.EnglishBooleanExtractorConfiguration; +import com.microsoft.recognizers.text.choice.extractors.BooleanExtractor; +import com.microsoft.recognizers.text.choice.models.BooleanModel; +import com.microsoft.recognizers.text.choice.parsers.BooleanParser; + +import java.util.List; + +public class ChoiceRecognizer extends Recognizer { + + public ChoiceRecognizer(String targetCulture, ChoiceOptions options, boolean lazyInitialization) { + super(targetCulture, options, lazyInitialization); + } + + public ChoiceRecognizer(String targetCulture, int options, boolean lazyInitialization) { + this(targetCulture, ChoiceOptions.values()[options], lazyInitialization); + } + + public ChoiceRecognizer(int options, boolean lazyInitialization) { + this(null, ChoiceOptions.values()[options], lazyInitialization); + } + + public ChoiceRecognizer(ChoiceOptions options, boolean lazyInitialization) { + this(null, options, lazyInitialization); + } + + public ChoiceRecognizer(boolean lazyInitialization) { + this(null, ChoiceOptions.None, lazyInitialization); + } + + public ChoiceRecognizer(int options) { + this(null, ChoiceOptions.values()[options], true); + } + + public ChoiceRecognizer(ChoiceOptions options) { + this(null, options, true); + } + + public ChoiceRecognizer() { + this(null, ChoiceOptions.None, true); + } + + public BooleanModel getBooleanModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(BooleanModel.class, culture, fallbackToDefaultCulture); + } + + public static List recognizeBoolean(String query, String culture, ChoiceOptions options, boolean fallbackToDefaultCulture) { + + ChoiceRecognizer recognizer = new ChoiceRecognizer(options); + IModel model = recognizer.getBooleanModel(culture, fallbackToDefaultCulture); + + return model.parse(query); + } + + public static List recognizeBoolean(String query, String culture, ChoiceOptions options) { + return recognizeBoolean(query, culture, options, true); + } + + public static List recognizeBoolean(String query, String culture) { + return recognizeBoolean(query, culture, ChoiceOptions.None); + } + + @Override + protected void initializeConfiguration() { + + //English + registerModel(BooleanModel.class, Culture.English, (options) -> new BooleanModel(new BooleanParser(), new BooleanExtractor(new EnglishBooleanExtractorConfiguration()))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java new file mode 100644 index 000000000..3db27c8dc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.choice; + +public class Constants { + public static final String SYS_BOOLEAN_TRUE = "boolean_true"; + public static final String SYS_BOOLEAN_FALSE = "boolean_false"; + // Model type name + public static final String MODEL_BOOLEAN = "boolean"; +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java new file mode 100644 index 000000000..e19e35402 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.choice.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.choice.Constants; + +import java.util.Map; + +public class BooleanParserConfiguration implements IChoiceParserConfiguration { + + public static Map Resolutions = ImmutableMap.builder() + .put(Constants.SYS_BOOLEAN_TRUE, true) + .put(Constants.SYS_BOOLEAN_FALSE, false) + .build(); + + @Override + public Map getResolutions() { + return Resolutions; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java new file mode 100644 index 000000000..d56112251 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.choice.config; + +import java.util.Map; + +public interface IChoiceParserConfiguration { + public Map getResolutions(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java new file mode 100644 index 000000000..8b2c4ab65 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java @@ -0,0 +1,70 @@ +package com.microsoft.recognizers.text.choice.english.extractors; + +import com.microsoft.recognizers.text.choice.Constants; +import com.microsoft.recognizers.text.choice.extractors.IBooleanExtractorConfiguration; +import com.microsoft.recognizers.text.choice.resources.EnglishChoice; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class EnglishBooleanExtractorConfiguration implements IBooleanExtractorConfiguration { + public static final Pattern trueRegex = RegExpUtility.getSafeRegExp(EnglishChoice.TrueRegex); + public static final Pattern falseRegex = RegExpUtility.getSafeRegExp(EnglishChoice.FalseRegex); + public static final Pattern tokenRegex = RegExpUtility.getSafeRegExp(EnglishChoice.TokenizerRegex); + public static final Map mapRegexes; + + static { + mapRegexes = new HashMap(); + mapRegexes.put(trueRegex, Constants.SYS_BOOLEAN_TRUE); + mapRegexes.put(falseRegex, Constants.SYS_BOOLEAN_FALSE); + } + + public boolean allowPartialMatch = false; + public int maxDistance = 2; + public boolean onlyTopMatch; + + public EnglishBooleanExtractorConfiguration(boolean topMatch) { + onlyTopMatch = topMatch; + } + + public EnglishBooleanExtractorConfiguration() { + this(true); + } + + @Override + public Map getMapRegexes() { + return mapRegexes; + } + + @Override + public Pattern getTrueRegex() { + return trueRegex; + } + + @Override + public Pattern getFalseRegex() { + return falseRegex; + } + + @Override + public Pattern getTokenRegex() { + return tokenRegex; + } + + @Override + public boolean getAllowPartialMatch() { + return allowPartialMatch; + } + + @Override + public int getMaxDistance() { + return maxDistance; + } + + @Override + public boolean getOnlyTopMatch() { + return onlyTopMatch; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java new file mode 100644 index 000000000..6f9b8036f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.choice.extractors; + +public class BooleanExtractor extends ChoiceExtractor { + + public BooleanExtractor(IBooleanExtractorConfiguration config) { + + super(config); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java new file mode 100644 index 000000000..7fd89481a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.util.ArrayList; +import java.util.List; + +public class ChoiceExtractDataResult { + + public final List otherMatches; + public final String source; + public final double score; + + public ChoiceExtractDataResult(String extractDataSource, double extractDataScore, List extractDataOtherMatches) { + otherMatches = extractDataOtherMatches; + source = extractDataSource; + score = extractDataScore; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java new file mode 100644 index 000000000..55f351bb9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java @@ -0,0 +1,170 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.choice.utilities.UnicodeUtils; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +public class ChoiceExtractor implements IExtractor { + + private IChoiceExtractorConfiguration config; + + public ChoiceExtractor(IChoiceExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String text) { + + List results = new ArrayList<>(); + String trimmedText = text.toLowerCase(); + List partialResults = new ArrayList<>(); + List sourceTokens = tokenize(trimmedText); + + if (text.isEmpty()) { + return results; + } + + for (Map.Entry entry : this.config.getMapRegexes().entrySet()) { + + Pattern regexKey = entry.getKey(); + String constantValue = entry.getValue(); + Match[] matches = RegExpUtility.getMatches(regexKey, trimmedText); + double topScore = 0; + + for (Match match : matches) { + + List matchToken = tokenize(match.value); + for (int i = 0; i < sourceTokens.size(); i++) { + double score = matchValue(sourceTokens, matchToken, i); + topScore = Math.max(topScore, score); + } + + if (topScore > 0.0) { + int start = match.index; + int length = match.length; + partialResults.add( + new ExtractResult( + start, + length, + text.substring(start, length + start), + constantValue, + new ChoiceExtractDataResult(text, topScore, new ArrayList<>()) + ) + ); + } + + } + } + + if (partialResults.size() == 0) { + return results; + } + + partialResults.sort(Comparator.comparingInt(er -> er.getStart())); + + if (this.config.getOnlyTopMatch()) { + + double topScore = 0; + int topResultIndex = 0; + + for (int i = 0; i < partialResults.size(); i++) { + + ChoiceExtractDataResult data = (ChoiceExtractDataResult)partialResults.get(i).getData(); + if (data.score > topScore) { + topScore = data.score; + topResultIndex = i; + } + + } + results.add(partialResults.get(topResultIndex)); + partialResults.remove(topResultIndex); + } else { + results = partialResults; + } + + return results; + } + + private final double matchValue(List source, List match, int startPosition) { + + double matched = 0; + double totalDeviation = 0; + double score = 0; + + for (String token : match) { + int pos = indexOfToken(source, token, startPosition); + if (pos >= 0) { + int distance = matched > 0 ? pos - startPosition : 0; + if (distance <= config.getMaxDistance()) { + matched++; + totalDeviation += distance; + startPosition = pos + 1; + } + } + } + + if (matched > 0 && (matched == match.size() || config.getAllowPartialMatch())) { + double completeness = matched / match.size(); + double accuracy = completeness * (matched / (matched + totalDeviation)); + double initialScore = accuracy * (matched / source.size()); + score = 0.4 + (0.6 * initialScore); + } + + return score; + } + + private static int indexOfToken(List tokens, String token, int startPos) { + + if (tokens.size() <= startPos) { + return -1; + } + + return tokens.indexOf(token); + } + + private final List tokenize(String text) { + + List tokens = new ArrayList<>(); + List letters = UnicodeUtils.letters(text); + String token = ""; + + for (String letter : letters) { + + Optional isMatch = Arrays.stream(RegExpUtility.getMatches(this.config.getTokenRegex(), letter)).findFirst(); + if (UnicodeUtils.isEmoji(letter)) { + + // Character is in a Supplementary Unicode Plane. This is where emoji live so + // we're going to just break each character in this range out as its own token. + tokens.add(letter); + if (!StringUtility.isNullOrWhiteSpace(token)) { + tokens.add(token); + token = ""; + } + + } else if (!(isMatch.isPresent() || StringUtility.isNullOrWhiteSpace(letter))) { + token = token + letter; + } else if (!StringUtility.isNullOrWhiteSpace(token)) { + tokens.add(token); + token = ""; + } + } + + if (!StringUtility.isNullOrWhiteSpace(token)) { + tokens.add(token); + token = ""; + } + + return tokens; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java new file mode 100644 index 000000000..fbcbe8e9a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import java.util.regex.Pattern; + +public interface IBooleanExtractorConfiguration extends IChoiceExtractorConfiguration { + public Pattern getTrueRegex(); + + public Pattern getFalseRegex(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java new file mode 100644 index 000000000..609a3b0d4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java @@ -0,0 +1,16 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import java.util.Map; +import java.util.regex.Pattern; + +public interface IChoiceExtractorConfiguration { + public Map getMapRegexes(); + + public Pattern getTokenRegex(); + + public boolean getAllowPartialMatch(); + + public int getMaxDistance(); + + public boolean getOnlyTopMatch(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java new file mode 100644 index 000000000..9c696f446 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.choice.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.choice.Constants; +import com.microsoft.recognizers.text.choice.parsers.OptionsOtherMatchParseResult; +import com.microsoft.recognizers.text.choice.parsers.OptionsParseDataResult; + +import java.util.SortedMap; +import java.util.TreeMap; + +public class BooleanModel extends ChoiceModel { + + public BooleanModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + public String getModelTypeName() { + return Constants.MODEL_BOOLEAN; + } + + @Override + protected SortedMap getResolution(ParseResult parseResult) { + + OptionsParseDataResult parseResultData = (OptionsParseDataResult)parseResult.getData(); + SortedMap results = new TreeMap(); + SortedMap otherMatchesMap = new TreeMap(); + + results.put("value", parseResult.getValue()); + results.put("score", parseResultData.score); + + for (OptionsOtherMatchParseResult otherMatchParseRes : parseResultData.otherMatches) { + otherMatchesMap.put("text", otherMatchParseRes.text); + otherMatchesMap.put("value", otherMatchParseRes.value); + otherMatchesMap.put("score", otherMatchParseRes.score); + } + + results.put("otherResults", otherMatchesMap); + + return results; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java new file mode 100644 index 000000000..516bb121c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.choice.models; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.choice.Constants; + +import java.util.List; +import java.util.SortedMap; +import java.util.stream.Collectors; + +public abstract class ChoiceModel implements IModel { + protected IExtractor extractor; + protected IParser parser; + + public ChoiceModel(IParser choiceParser, IExtractor choiceExtractor) { + parser = choiceParser; + extractor = choiceExtractor; + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_BOOLEAN; + } + + @Override + public List parse(String query) { + + List extractResults = extractor.extract(query); + List parseResults = extractResults.stream().map(exRes -> parser.parse(exRes)).collect(Collectors.toList()); + + List modelResults = parseResults.stream().map( + parseRes -> new ModelResult(parseRes.getText(), parseRes.getStart(), parseRes.getStart() + parseRes.getLength() - 1, getModelTypeName(), getResolution(parseRes)) + ).collect(Collectors.toList()); + + return modelResults; + } + + protected abstract SortedMap getResolution(ParseResult parseResult); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java new file mode 100644 index 000000000..f0ce82382 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.choice.parsers; + +import com.microsoft.recognizers.text.choice.config.BooleanParserConfiguration; + +public class BooleanParser extends ChoiceParser { + public BooleanParser() { + super(new BooleanParserConfiguration()); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java new file mode 100644 index 000000000..91316e36a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.choice.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.choice.config.IChoiceParserConfiguration; +import com.microsoft.recognizers.text.choice.extractors.ChoiceExtractDataResult; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ChoiceParser implements IParser { + + private IChoiceParserConfiguration config; + + public ChoiceParser(IChoiceParserConfiguration config) { + this.config = config; + } + + public ParseResult parse(ExtractResult extractResult) { + + ParseResult parseResult = new ParseResult(extractResult); + ChoiceExtractDataResult data = (ChoiceExtractDataResult)extractResult.getData(); + Map resolutions = this.config.getResolutions(); + List matches = data.otherMatches.stream().map(match -> getOptionsOtherMatchResult(match)).collect(Collectors.toList()); + + parseResult.setData(new OptionsParseDataResult(data.score, matches)); + parseResult.setValue(resolutions.getOrDefault(parseResult.getType(), false)); + + return parseResult; + } + + private OptionsOtherMatchParseResult getOptionsOtherMatchResult(ExtractResult extractResult) { + + ParseResult parseResult = new ParseResult(extractResult); + ChoiceExtractDataResult data = (ChoiceExtractDataResult)extractResult.getData(); + Map resolutions = this.config.getResolutions(); + OptionsOtherMatchParseResult result = new OptionsOtherMatchParseResult(parseResult.getText(), resolutions.get(parseResult.getType()), data.score); + + return result; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java new file mode 100644 index 000000000..dd66f24b8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text.choice.parsers; + +public class OptionsOtherMatchParseResult { + + public final double score; + public final String text; + public final Object value; + + public OptionsOtherMatchParseResult(String parseResultText, Object parseResultValue, double parseResultScore) { + score = parseResultScore; + text = parseResultText; + value = parseResultValue; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java new file mode 100644 index 000000000..bd08beaa8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.choice.parsers; + +import java.util.ArrayList; +import java.util.List; + +public class OptionsParseDataResult { + + public final double score; + public final List otherMatches; + + public OptionsParseDataResult(double optionScore, List optionOtherMatches) { + score = optionScore; + otherMatches = optionOtherMatches; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java new file mode 100644 index 000000000..19fa19526 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class ChineseChoice { + + public static final String LangMarker = "Chs"; + + public static final String TokenizerRegex = "[^\\u3040-\\u30ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\uff66-\\uff9f]"; + + public static final String TrueRegex = "(好[的啊呀嘞哇]|没问题|可以|中|好|同意|行|是的|是|对)|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "(不行|不好|拒绝|否定|不中|不可以|不是的|不是|不对|不)|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java new file mode 100644 index 000000000..440e5a162 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class EnglishChoice { + + public static final String LangMarker = "Eng"; + + public static final String TokenizerRegex = "[^\\w\\d]"; + + public static final String TrueRegex = "\\b(true|yes|yep|yup|yeah|y|sure|ok|agree)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C|\\u0001f44c)"; + + public static final String FalseRegex = "\\b(false|nope|nop|no|not\\s+ok|disagree)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90|\\u0001F44E|\\u0001F590)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java new file mode 100644 index 000000000..7dcff1c3a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class FrenchChoice { + + public static final String LangMarker = "Fr"; + + public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; + + public static final String TrueRegex = "\\b(s[uû]r|ouais|oui|yep|y|sure|approuver|accepter|consentir|d'accord|ça march[eé])\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "\\b(faux|nan|non|pas\\s+d'accord|pas\\s+concorder|n'est\\s+pas\\s+(correct|ok)|pas)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java new file mode 100644 index 000000000..7c6b48ef0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class PortugueseChoice { + + public static final String LangMarker = "Por"; + + public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; + + public static final String TrueRegex = "\\b(verdade|verdadeir[oa]|sim|isso|claro|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "\\b(falso|n[aã]o|incorreto|nada disso)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java new file mode 100644 index 000000000..d3cd40413 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class SpanishChoice { + + public static final String LangMarker = "Spa"; + + public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; + + public static final String TrueRegex = "\\b(verdad|verdadero|sí|sip|s|si|cierto|por supuesto|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "\\b(falso|no|nop|n|no)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java new file mode 100644 index 000000000..fe7a281a4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.choice.utilities; + +import java.lang.Character; +import java.util.ArrayList; +import java.util.List; + +public class UnicodeUtils { + public static boolean isEmoji(String letter) { + final int WhereEmojiLive = 0xFFFF; // Supplementary Unicode Plane. This is where emoji live + return Character.isHighSurrogate(letter.charAt(0)) && (letter.codePoints().sum() > WhereEmojiLive); + } + + public static List letters(String text) { + char codePoint = 0; + List result = new ArrayList<>(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (codePoint != 0) { + result.add(new String(Character.toChars(codePoint + c))); + codePoint = 0; + } else if (!Character.isHighSurrogate(c)) { + result.add(Character.toString(c)); + } else { + codePoint = c; + } + } + return result; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java new file mode 100644 index 000000000..5046497d0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java @@ -0,0 +1,182 @@ +package com.microsoft.recognizers.text.datetime; + +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; + +public class Constants { + + public static final String SYS_DATETIME_DATE = "date"; + public static final String SYS_DATETIME_TIME = "time"; + public static final String SYS_DATETIME_DATEPERIOD = "daterange"; + public static final String SYS_DATETIME_DATETIME = "datetime"; + public static final String SYS_DATETIME_TIMEPERIOD = "timerange"; + public static final String SYS_DATETIME_DATETIMEPERIOD = "datetimerange"; + public static final String SYS_DATETIME_DURATION = "duration"; + public static final String SYS_DATETIME_SET = "set"; + public static final String SYS_DATETIME_DATETIMEALT = "datetimealt"; + public static final String SYS_DATETIME_TIMEZONE = "timezone"; + + // SourceEntity Types + public static final String SYS_DATETIME_DATETIMEPOINT = "datetimepoint"; + + // Model Name + public static final String MODEL_DATETIME = "datetime"; + + // Multiple Duration Types + public static final String MultipleDuration_Prefix = "multipleDuration"; + public static final String MultipleDuration_Type = MultipleDuration_Prefix + "Type"; + public static final String MultipleDuration_DateTime = MultipleDuration_Prefix + "DateTime"; + public static final String MultipleDuration_Date = MultipleDuration_Prefix + "Date"; + public static final String MultipleDuration_Time = MultipleDuration_Prefix + "Time"; + + // DateTime Parse + public static final String Resolve = "resolve"; + public static final String ResolveToPast = "resolveToPast"; + public static final String ResolveToFuture = "resolveToFuture"; + public static final String FutureDate = "futureDate"; + public static final String PastDate = "pastDate"; + public static final String ParseResult1 = "parseResult1"; + public static final String ParseResult2 = "parseResult2"; + + // In the ExtractResult data + public static final String Context = "context"; + public static final String ContextType_RelativePrefix = "relativePrefix"; + public static final String ContextType_RelativeSuffix = "relativeSuffix"; + public static final String ContextType_AmPm = "AmPm"; + public static final String SubType = "subType"; + + // Comment - internal tag used during entity processing, never exposed to users. + // Tags are filtered out in BaseMergedDateTimeParser DateTimeResolution() + public static final String Comment = "Comment"; + // AmPm time representation for time parser + public static final String Comment_AmPm = "ampm"; + // Prefix early/late for time parser + public static final String Comment_Early = "early"; + public static final String Comment_Late = "late"; + // Parse week of date format + public static final String Comment_WeekOf = "WeekOf"; + public static final String Comment_MonthOf = "MonthOf"; + + public static final String Comment_DoubleTimex = "doubleTimex"; + + public static final String InvalidDateString = "0001-01-01"; + public static final String CompositeTimexDelimiter = "|"; + public static final String CompositeTimexSplit = "\\|"; + + // Mod Value + // "before" -> To mean "preceding in time". I.e. Does not include the extracted datetime entity in the resolution's ending point. Equivalent to "<" + public static final String BEFORE_MOD = "before"; + + // "after" -> To mean "following in time". I.e. Does not include the extracted datetime entity in the resolution's starting point. Equivalent to ">" + public static final String AFTER_MOD = "after"; + + // "since" -> Same as "after", but including the extracted datetime entity. Equivalent to ">=" + public static final String SINCE_MOD = "since"; + + // "until" -> Same as "before", but including the extracted datetime entity. Equivalent to "<=" + public static final String UNTIL_MOD = "until"; + + public static final String EARLY_MOD = "start"; + public static final String MID_MOD = "mid"; + public static final String LATE_MOD = "end"; + + public static final String MORE_THAN_MOD = "more"; + public static final String LESS_THAN_MOD = "less"; + + public static final String REF_UNDEF_MOD = "ref_undef"; + + public static final String APPROX_MOD = "approx"; + + // Invalid year + public static final int InvalidYear = Integer.MIN_VALUE; + public static final int InvalidMonth = Integer.MIN_VALUE; + public static final int InvalidDay = Integer.MIN_VALUE; + public static final int InvalidHour = Integer.MIN_VALUE; + public static final int InvalidMinute = Integer.MIN_VALUE; + public static final int InvalidSecond = Integer.MIN_VALUE; + + public static final int MinYearNum = BaseDateTime.MinYearNum; + public static final int MaxYearNum = BaseDateTime.MaxYearNum; + + public static final int MaxTwoDigitYearFutureNum = BaseDateTime.MaxTwoDigitYearFutureNum; + public static final int MinTwoDigitYearPastNum = BaseDateTime.MinTwoDigitYearPastNum; + + // These are some particular values for timezone recognition + public static final int InvalidOffsetValue = -10000; + public static final String UtcOffsetMinsKey = "utcOffsetMins"; + public static final String TimeZoneText = "timezoneText"; + public static final String TimeZone = "timezone"; + public static final String ResolveTimeZone = "resolveTimeZone"; + public static final int PositiveSign = 1; + public static final int NegativeSign = -1; + + public static final int TrimesterMonthCount = 3; + public static final int QuarterCount = 4; + public static final int SemesterMonthCount = 6; + public static final int WeekDayCount = 7; + public static final int CenturyYearsCount = 100; + public static final int MaxWeekOfMonth = 5; + + // hours of one half day + public static final int HalfDayHourCount = 12; + // hours of a half mid-day-duration + public static final int HalfMidDayDurationHourCount = 2; + + // the length of four digits year, e.g., 2018 + public static final int FourDigitsYearLength = 4; + + // specifies the priority interpreting month and day order + public static final String DefaultLanguageFallback_MDY = "MDY"; + public static final String DefaultLanguageFallback_DMY = "DMY"; + public static final String DefaultLanguageFallback_YMD = "YMD"; // ZH + + // Groups' names for named groups in regexes + public static final String NextGroupName = "next"; + public static final String AmGroupName = "am"; + public static final String PmGroupName = "pm"; + public static final String ImplicitAmGroupName = "iam"; + public static final String ImplicitPmGroupName = "ipm"; + public static final String PrefixGroupName = "prefix"; + public static final String SuffixGroupName = "suffix"; + public static final String DescGroupName = "desc"; + public static final String SecondGroupName = "sec"; + public static final String MinuteGroupName = "min"; + public static final String HourGroupName = "hour"; + public static final String TimeOfDayGroupName = "timeOfDay"; + public static final String BusinessDayGroupName = "business"; + public static final String LeftAmPmGroupName = "leftDesc"; + public static final String RightAmPmGroupName = "rightDesc"; + + public static final String DECADE_UNIT = "10Y"; + public static final String FORTNIGHT_UNIT = "2W"; + + // Timex + public static final String[] DatePeriodTimexSplitter = { ",", "(", ")" }; + public static final String TimexYear = "Y"; + public static final String TimexMonth = "M"; + public static final String TimexMonthFull = "MON"; + public static final String TimexWeek = "W"; + public static final String TimexDay = "D"; + public static final String TimexBusinessDay = "BD"; + public static final String TimexWeekend = "WE"; + public static final String TimexHour = "H"; + public static final String TimexMinute = "M"; + public static final String TimexSecond = "S"; + public static final char TimexFuzzy = 'X'; + public static final String TimexFuzzyYear = "XXXX"; + public static final String TimexFuzzyMonth = "XX"; + public static final String TimexFuzzyWeek = "WXX"; + public static final String TimexFuzzyDay = "XX"; + public static final String DateTimexConnector = "-"; + public static final String TimeTimexConnector = ":"; + public static final String GeneralPeriodPrefix = "P"; + public static final String TimeTimexPrefix = "T"; + + // Timex of TimeOfDay + public static final String EarlyMorning = "TDA"; + public static final String Morning = "TMO"; + public static final String Afternoon = "TAF"; + public static final String Evening = "TEV"; + public static final String Daytime = "TDT"; + public static final String Night = "TNI"; + public static final String BusinessHour = "TBH"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java new file mode 100644 index 000000000..853934357 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.datetime; + +public enum DatePeriodTimexType { + ByDay, + ByWeek, + ByMonth, + ByYear +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java new file mode 100644 index 000000000..5519bd005 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java @@ -0,0 +1,26 @@ +package com.microsoft.recognizers.text.datetime; + +public enum DateTimeOptions { + None(0), + SkipFromToMerge(1), + SplitDateAndTime(2), + CalendarMode(4), + ExtendedTypes(8), + EnablePreview(8388608), + ExperimentalMode(4194304), + ComplexCalendar(8 + 4 + 8388608); + + private final int value; + + DateTimeOptions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public boolean match(DateTimeOptions option) { + return (this.value & option.value) == option.value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java new file mode 100644 index 000000000..9d4c0adf1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java @@ -0,0 +1,93 @@ +package com.microsoft.recognizers.text.datetime; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.parsers.EnglishMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseMergedDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.parsers.FrenchMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.models.DateTimeModel; +import com.microsoft.recognizers.text.datetime.parsers.BaseMergedDateTimeParser; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.parsers.SpanishMergedParserConfiguration; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.function.Function; + +public class DateTimeRecognizer extends Recognizer { + + public DateTimeRecognizer() { + this(null, DateTimeOptions.None, true); + } + + public DateTimeRecognizer(String culture) { + this(culture, DateTimeOptions.None, false); + } + + public DateTimeRecognizer(DateTimeOptions options) { + this(null, options, true); + } + + public DateTimeRecognizer(DateTimeOptions options, boolean lazyInitialization) { + this(null, options, lazyInitialization); + } + + public DateTimeRecognizer(String culture, DateTimeOptions options, boolean lazyInitialization) { + super(culture, options, lazyInitialization); + } + + public DateTimeModel getDateTimeModel() { + return getDateTimeModel(null, true); + } + + public DateTimeModel getDateTimeModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(DateTimeModel.class, culture, fallbackToDefaultCulture); + } + + //region Helper methods for less verbosity + public static List recognizeDateTime(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, true), query, DateTimeOptions.None, LocalDateTime.now()); + } + + public static List recognizeDateTime(String query, String culture, DateTimeOptions options) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, true), query, options, LocalDateTime.now()); + } + + public static List recognizeDateTime(String query, String culture, DateTimeOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, fallbackToDefaultCulture), query, options, LocalDateTime.now()); + } + + public static List recognizeDateTime(String query, String culture, DateTimeOptions options, boolean fallbackToDefaultCulture, LocalDateTime reference) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, fallbackToDefaultCulture), query, options, reference); + } + //endregion + + private static List recognizeByModel(Function getModelFun, String query, DateTimeOptions options, LocalDateTime reference) { + DateTimeRecognizer recognizer = new DateTimeRecognizer(options); + DateTimeModel model = getModelFun.apply(recognizer); + return model.parse(query, reference); + } + + @Override + protected void initializeConfiguration() { + // English + registerModel(DateTimeModel.class, Culture.English, + (options) -> new DateTimeModel( + new BaseMergedDateTimeParser(new EnglishMergedParserConfiguration(options)), + new BaseMergedDateTimeExtractor(new EnglishMergedExtractorConfiguration(options)))); + + // Spanish + registerModel(DateTimeModel.class, Culture.Spanish, + (options) -> new DateTimeModel( + new BaseMergedDateTimeParser(new SpanishMergedParserConfiguration(options)), + new BaseMergedDateTimeExtractor(new SpanishMergedExtractorConfiguration(options)))); + + registerModel(DateTimeModel.class, Culture.French, dateTimeOptions -> new DateTimeModel( + new BaseMergedDateTimeParser(new FrenchMergedParserConfiguration(options)), + new BaseMergedDateTimeExtractor(new FrenchMergedExtractorConfiguration(options)) + )); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java new file mode 100644 index 000000000..e29f7c08f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.datetime; + +public class DateTimeResolutionKey { + public static final String Timex = "timex"; + public static final String Mod = "Mod"; + public static final String IsLunar = "isLunar"; + public static final String START = "start"; + public static final String END = "end"; + public static final String List = "list"; + public static final String SourceEntity = "sourceEntity"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java new file mode 100644 index 000000000..914b8b3a7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.datetime; + +public class TimeTypeConstants { + public static final String DATE = "date"; + public static final String DATETIME = "dateTime"; + public static final String DATETIMEALT = "dateTimeAlt"; + public static final String DURATION = "duration"; + public static final String SET = "set"; + public static final String TIME = "time"; + + // Internal SubType for Future/Past in DateTimeResolutionResult + public static final String START_DATE = "startDate"; + public static final String END_DATE = "endDate"; + public static final String START_DATETIME = "startDateTime"; + public static final String END_DATETIME = "endDateTime"; + public static final String START_TIME = "startTime"; + public static final String END_TIME = "endTime"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java new file mode 100644 index 000000000..36fb5ac88 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.config; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; + +public class BaseOptionsConfiguration implements IOptionsConfiguration { + private final DateTimeOptions options; + private final boolean dmyDateFormat; + + public BaseOptionsConfiguration() { + this(DateTimeOptions.None, false); + } + + public BaseOptionsConfiguration(DateTimeOptions options) { + this(options, false); + } + + public BaseOptionsConfiguration(DateTimeOptions options, boolean dmyDateFormat) { + this.options = options; + this.dmyDateFormat = dmyDateFormat; + } + + @Override + public DateTimeOptions getOptions() { + return options; + } + + @Override + public boolean getDmyDateFormat() { + return dmyDateFormat; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java new file mode 100644 index 000000000..45d3f3f0c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text.datetime.config; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; + +public interface IOptionsConfiguration { + DateTimeOptions getOptions(); + + boolean getDmyDateFormat(); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java new file mode 100644 index 000000000..588917bf8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java @@ -0,0 +1,241 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class EnglishDateExtractorConfiguration extends BaseOptionsConfiguration implements IDateExtractorConfiguration { + + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ImplicitDayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthNumRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearRegex); + public static final Pattern WeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayRegex); + public static final Pattern SingleWeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SingleWeekDayRegex); + public static final Pattern OnRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OnRegex); + public static final Pattern RelaxedOnRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelaxedOnRegex); + public static final Pattern ThisRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ThisRegex); + public static final Pattern LastDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LastDateRegex); + public static final Pattern NextDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextDateRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern SpecialDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecialDayRegex); + public static final Pattern WeekDayOfMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayOfMonthRegex); + public static final Pattern RelativeWeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeWeekDayRegex); + public static final Pattern SpecialDate = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecialDate); + public static final Pattern SpecialDayWithNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecialDayWithNumRegex); + public static final Pattern ForTheRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ForTheRegex); + public static final Pattern WeekDayAndDayOfMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayAndDayOfMonthRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeMonthRegex); + public static final Pattern StrictRelativeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.StrictRelativeRegex); + public static final Pattern PrefixArticleRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrefixArticleRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InConnectorRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeUnitRegex); + public static final Pattern RangeConnectorSymbolRegex = RegExpUtility.getSafeRegExp(BaseDateTime.RangeConnectorSymbolRegex); + + public static final List DateRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor1)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor3)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor4)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor5)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor6)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor7L)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor7S)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor8)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor9L)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor9S)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractorA)); + } + }; + + public static final List ImplicitDateList = new ArrayList() { + { + add(OnRegex); + add(RelaxedOnRegex); + add(SpecialDayRegex); + add(ThisRegex); + add(LastDateRegex); + add(NextDateRegex); + add(SingleWeekDayRegex); + add(WeekDayOfMonthRegex); + add(SpecialDate); + add(SpecialDayWithNumRegex); + add(RelativeWeekDayRegex); + } + }; + + public static final Pattern OfMonth = RegExpUtility.getSafeRegExp(EnglishDateTime.OfMonth); + public static final Pattern MonthEnd = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthEnd); + public static final Pattern WeekDayEnd = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayEnd); + public static final Pattern YearSuffix = RegExpUtility.getSafeRegExp(EnglishDateTime.YearSuffix); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MoreThanRegex); + + public static final ImmutableMap DayOfWeek = EnglishDateTime.DayOfWeek; + public static final ImmutableMap MonthOfYear = EnglishDateTime.MonthOfYear; + + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final List implicitDateList; + + public EnglishDateExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + integerExtractor = IntegerExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + + implicitDateList = new ArrayList<>(ImplicitDateList); + if (this.getOptions().match(DateTimeOptions.CalendarMode)) { + implicitDateList.add(DayRegex); + } + } + + @Override + public Iterable getDateRegexList() { + return DateRegexList; + } + + @Override + public Iterable getImplicitDateList() { + return implicitDateList; + } + + @Override + public Pattern getOfMonth() { + return OfMonth; + } + + @Override + public Pattern getMonthEnd() { + return MonthEnd; + } + + @Override + public Pattern getWeekDayEnd() { + return WeekDayEnd; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getForTheRegex() { + return ForTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return WeekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return RelativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return StrictRelativeRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return WeekDayRegex; + } + + @Override + public Pattern getPrefixArticleRegex() { + return PrefixArticleRegex; + } + + @Override + public Pattern getYearSuffix() { + return YearSuffix; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getRangeConnectorSymbolRegex() { + return RangeConnectorSymbolRegex; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfWeek() { + return DayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return MonthOfYear; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..2e2019b79 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java @@ -0,0 +1,311 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishDatePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDatePeriodExtractorConfiguration { + + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TillRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern FollowedDateUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.FollowedDateUnit); + public static final Pattern NumberCombinedWithDateUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberCombinedWithDateUnit); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + public static final Pattern FutureSuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.FutureSuffixRegex); + public static final Pattern WeekOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekOfRegex); + public static final Pattern MonthOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthOfRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeUnitRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WithinNextPrefixRegex); + public static final Pattern YearPeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearPeriodRegex); + public static final Pattern RelativeDecadeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeDecadeRegex); + public static final Pattern ComplexDatePeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ComplexDatePeriodRegex); + public static final Pattern ReferenceDatePeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ReferenceDatePeriodRegex); + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LaterRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MoreThanRegex); + public static final Pattern CenturySuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.CenturySuffixRegex); + public static final Pattern IllegalYearRegex = RegExpUtility.getSafeRegExp(BaseDateTime.IllegalYearRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NowRegex); + + // composite regexes + public static final Pattern SimpleCasesRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SimpleCasesRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.BetweenRegex); + public static final Pattern OneWordPeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OneWordPeriodRegex); + public static final Pattern MonthWithYear = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthWithYear); + public static final Pattern MonthNumWithYear = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthNumWithYear); + public static final Pattern WeekOfMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekOfMonthRegex); + public static final Pattern WeekOfYearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekOfYearRegex); + public static final Pattern MonthFrontBetweenRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthFrontBetweenRegex); + public static final Pattern MonthFrontSimpleCasesRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthFrontSimpleCasesRegex); + public static final Pattern QuarterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.QuarterRegex); + public static final Pattern QuarterRegexYearFront = RegExpUtility.getSafeRegExp(EnglishDateTime.QuarterRegexYearFront); + public static final Pattern AllHalfYearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AllHalfYearRegex); + public static final Pattern SeasonRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SeasonRegex); + public static final Pattern WhichWeekRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WhichWeekRegex); + public static final Pattern RestOfDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RestOfDateRegex); + public static final Pattern LaterEarlyPeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LaterEarlyPeriodRegex); + public static final Pattern WeekWithWeekDayRangeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekWithWeekDayRangeRegex); + public static final Pattern YearPlusNumberRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearPlusNumberRegex); + public static final Pattern DecadeWithCenturyRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DecadeWithCenturyRegex); + + public static final Iterable SimpleCasesRegexes = new ArrayList() { + { + add(SimpleCasesRegex); + add(BetweenRegex); + add(OneWordPeriodRegex); + add(MonthWithYear); + add(MonthNumWithYear); + add(YearRegex); + add(WeekOfMonthRegex); + add(WeekOfYearRegex); + add(MonthFrontBetweenRegex); + add(MonthFrontSimpleCasesRegex); + add(QuarterRegex); + add(QuarterRegexYearFront); + add(AllHalfYearRegex); + add(SeasonRegex); + add(WhichWeekRegex); + add(RestOfDateRegex); + add(LaterEarlyPeriodRegex); + add(WeekWithWeekDayRangeRegex); + add(YearPlusNumberRegex); + add(DecadeWithCenturyRegex); + add(RelativeDecadeRegex); + add(ReferenceDatePeriodRegex); + } + }; + + public static final Pattern rangeConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeConnectorRegex); + private final String[] durationDateRestrictions = EnglishDateTime.DurationDateRestrictions.toArray(new String[0]); + + private final IDateTimeExtractor datePointExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IParser numberParser; + + public EnglishDatePeriodExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + + datePointExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + cardinalExtractor = CardinalExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + } + + @Override + public Iterable getSimpleCasesRegexes() { + return SimpleCasesRegexes; + } + + @Override + public Pattern getIllegalYearRegex() { + return IllegalYearRegex; + } + + @Override + public Pattern getYearRegex() { + return YearRegex; + } + + @Override + public Pattern getTillRegex() { + return TillRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getFollowedDateUnit() { + return FollowedDateUnit; + } + + @Override + public Pattern getNumberCombinedWithDateUnit() { + return NumberCombinedWithDateUnit; + } + + @Override + public Pattern getPastRegex() { + return PreviousPrefixRegex; + } + + @Override + public Pattern getFutureRegex() { + return NextPrefixRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FutureSuffixRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return WeekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return MonthOfRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return YearPeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return RelativeDecadeRegex; + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return ComplexDatePeriodRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return ReferenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return CenturySuffixRegex; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public String[] getDurationDateRestrictions() { + return durationDateRestrictions; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("from")) { + result = true; + index = text.lastIndexOf("from"); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("between")) { + result = true; + index = text.lastIndexOf("between"); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + return RegexExtension.isExactMatch(rangeConnectorRegex, text, true); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..75c75d11e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java @@ -0,0 +1,90 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishDateTimeAltExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeAltExtractorConfiguration { + + private static final Pattern OrRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OrRegex); + private static final Pattern DayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DayRegex); + private static final Pattern RangePrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangePrefixRegex); + + public static final Pattern ThisPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ThisPrefixRegex); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmRegex); + + public static final Iterable RelativePrefixList = new ArrayList() { + { + add(ThisPrefixRegex); + add(PreviousPrefixRegex); + add(NextPrefixRegex); + } + }; + + public static final Iterable AmPmRegexList = new ArrayList() { + { + add(AmRegex); + add(PmRegex); + } + }; + + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor datePeriodExtractor; + + public EnglishDateTimeAltExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public Iterable getRelativePrefixList() { + return RelativePrefixList; + } + + @Override + public Iterable getAmPmRegexList() { + return AmPmRegexList; + } + + @Override + public Pattern getOrRegex() { + return OrRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return ThisPrefixRegex; + } + + @Override + public Pattern getDayRegex() { + return DayRegex; + } + + @Override public Pattern getRangePrefixRegex() { + return RangePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..317ade20d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java @@ -0,0 +1,160 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.regex.Pattern; + +public class EnglishDateTimeExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeExtractorConfiguration { + + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrepositionRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NowRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfDayRegex); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfTodayAfterRegex); + public static final Pattern TimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfTodayBeforeRegex); + public static final Pattern SimpleTimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SimpleTimeOfTodayAfterRegex); + public static final Pattern SimpleTimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SimpleTimeOfTodayBeforeRegex); + public static final Pattern SpecificEndOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificEndOfRegex); + public static final Pattern UnspecificEndOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.UnspecificEndOfRegex); + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern ConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ConnectorRegex); + public static final Pattern NumberAsTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberAsTimeRegex); + public static final Pattern DateNumberConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateNumberConnectorRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixAfterRegex); + + public IExtractor integerExtractor; + public IDateTimeExtractor datePointExtractor; + public IDateTimeExtractor timePointExtractor; + public IDateTimeExtractor durationExtractor; + public IDateTimeUtilityConfiguration utilityConfiguration; + + public EnglishDateTimeExtractorConfiguration(DateTimeOptions options) { + + super(options); + + integerExtractor = IntegerExtractor.getInstance(); + datePointExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + timePointExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(options)); + + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + } + + public EnglishDateTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getTimeOfTodayAfterRegex() { + return TimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return SimpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getTimeOfTodayBeforeRegex() { + return TimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return SimpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return SpecificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return UnspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return UnitRegex; + } + + @Override + public Pattern getNumberAsTimeRegex() { + return NumberAsTimeRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return DateNumberConnectorRegex; + } + + @Override + public Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IDateTimeExtractor getTimePointExtractor() { + return timePointExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + public boolean isConnector(String text) { + + text = text.trim(); + + boolean isPreposition = Arrays.stream(RegExpUtility.getMatches(PrepositionRegex, text)).findFirst().isPresent(); + boolean isConnector = Arrays.stream(RegExpUtility.getMatches(ConnectorRegex, text)).findFirst().isPresent(); + return (StringUtility.isNullOrEmpty(text) || isPreposition || isConnector); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..6ddc07c71 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,281 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishDateTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodExtractorConfiguration { + + public static final Iterable SimpleCases = new ArrayList() { + { + add(EnglishTimePeriodExtractorConfiguration.PureNumFromTo); + add(EnglishTimePeriodExtractorConfiguration.PureNumBetweenAnd); + } + }; + + public static final Pattern PeriodTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodTimeOfDayRegex); + public static final Pattern PeriodSpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodSpecificTimeOfDayRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeFollowedUnit); + public static final Pattern TimeNumberCombinedWithUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeNumberCombinedWithUnit); + public static final Pattern PeriodTimeOfDayWithDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodTimeOfDayWithDateRegex); + public static final Pattern RelativeTimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeTimeUnitRegex); + public static final Pattern RestOfDateTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RestOfDateTimeRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.GeneralEndingRegex); + public static final Pattern MiddlePauseRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MiddlePauseRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmDescRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WithinNextPrefixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern PrefixDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrefixDayRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfterRegex); + + private final String tokenBeforeDate; + + private final IExtractor cardinalExtractor; + private final IDateTimeExtractor singleDateExtractor; + private final IDateTimeExtractor singleTimeExtractor; + private final IDateTimeExtractor singleDateTimeExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + private final Pattern weekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayRegex); + private final Pattern rangeConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeConnectorRegex); + + public EnglishDateTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishDateTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + + //TODO add english implementations + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + + cardinalExtractor = CardinalExtractor.getInstance(); + singleDateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + singleTimeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + singleDateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(options)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public Iterable getSimpleCasesRegex() { + return SimpleCases; + } + + @Override + public Pattern getPrepositionRegex() { + return EnglishTimePeriodExtractorConfiguration.PrepositionRegex; + } + + @Override + public Pattern getTillRegex() { + return EnglishTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return PeriodSpecificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return PeriodTimeOfDayRegex; + } + + @Override + public Pattern getFollowedUnit() { + return TimeFollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return TimeNumberCombinedWithUnit; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return EnglishDatePeriodExtractorConfiguration.PreviousPrefixRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return EnglishDatePeriodExtractorConfiguration.NextPrefixRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return EnglishDatePeriodExtractorConfiguration.FutureSuffixRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return PeriodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return RelativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return RestOfDateTimeRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public Pattern getMiddlePauseRegex() { + return MiddlePauseRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return PrefixDayRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getBeforeRegex() { + return BeforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return AfterRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateExtractor() { + return singleDateExtractor; + } + + @Override + public IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateTimeExtractor() { + return singleDateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + // TODO: these three methods are the same in DatePeriod, should be abstracted + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("from")) { + result = true; + index = text.lastIndexOf("from"); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("between")) { + result = true; + index = text.lastIndexOf("between"); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + return RegexExtension.isExactMatch(rangeConnectorRegex, text, true); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java new file mode 100644 index 000000000..773a600ef --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java @@ -0,0 +1,138 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDurationExtractorConfiguration extends BaseOptionsConfiguration implements IDurationExtractorConfiguration { + + public static final Pattern DurationUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationUnitRegex); + public static final Pattern SuffixAndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixAndRegex); + public static final Pattern DurationFollowedUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationFollowedUnit); + public static final Pattern NumberCombinedWithDurationUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberCombinedWithDurationUnit); + public static final Pattern AnUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AnUnitRegex); + public static final Pattern DuringRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DuringRegex); + public static final Pattern AllRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AllRegex); + public static final Pattern HalfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.HalfRegex); + public static final Pattern ConjunctionRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ConjunctionRegex); + public static final Pattern InexactNumberRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InexactNumberRegex); + public static final Pattern InexactNumberUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InexactNumberUnitRegex); + public static final Pattern RelativeDurationUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeDurationUnitRegex); + public static final Pattern DurationConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationConnectorRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MoreThanRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanRegex); + + private final IExtractor cardinalExtractor; + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + + public EnglishDurationExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishDurationExtractorConfiguration(DateTimeOptions options) { + + super(options); + + cardinalExtractor = CardinalExtractor.getInstance(); + unitMap = EnglishDateTime.UnitMap; + unitValueMap = EnglishDateTime.UnitValueMap; + } + + @Override + public Pattern getFollowedUnit() { + return DurationFollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithDurationUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return AnUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return DuringRegex; + } + + @Override + public Pattern getAllRegex() { + return AllRegex; + } + + @Override + public Pattern getHalfRegex() { + return HalfRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return SuffixAndRegex; + } + + @Override + public Pattern getConjunctionRegex() { + return ConjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return InexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return InexactNumberUnitRegex; + } + + @Override + public Pattern getRelativeDurationUnitRegex() { + return RelativeDurationUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return DurationUnitRegex; + } + + @Override + public Pattern getDurationConnectorRegex() { + return DurationConnectorRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java new file mode 100644 index 000000000..7843fda3a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishHolidayExtractorConfiguration extends BaseOptionsConfiguration implements IHolidayExtractorConfiguration { + + public static final Pattern YearPattern = RegExpUtility.getSafeRegExp(EnglishDateTime.YearRegex); + + public static final Pattern H = RegExpUtility.getSafeRegExp(EnglishDateTime.HolidayRegex); + + public static final Iterable HolidayRegexList = new ArrayList() { + { + add(H); + } + }; + + public EnglishHolidayExtractorConfiguration() { + super(DateTimeOptions.None); + } + + public Iterable getHolidayRegexes() { + return HolidayRegexList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java new file mode 100644 index 000000000..dd083d202 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java @@ -0,0 +1,220 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeAltExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseHolidayExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseSetExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +import org.javatuples.Pair; + +public class EnglishMergedExtractorConfiguration extends BaseOptionsConfiguration implements IMergedExtractorConfiguration { + + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfterRegex); + public static final Pattern SinceRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SinceRegex); + public static final Pattern AroundRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AroundRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.BeforeRegex); + public static final Pattern FromToRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.FromToRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixAfterRegex); + public static final Pattern NumberEndingPattern = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberEndingPattern); + public static final Pattern PrepositionSuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrepositionSuffixRegex); + public static final Pattern AmbiguousRangeModifierPrefix = RegExpUtility.getSafeRegExp(EnglishDateTime.AmbiguousRangeModifierPrefix); + public static final Pattern SingleAmbiguousMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SingleAmbiguousMonthRegex); + public static final Pattern UnspecificDatePeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.UnspecificDatePeriodRegex); + private final Iterable> ambiguityFiltersDict; + + public static final StringMatcher SuperfluousWordMatcher = new StringMatcher(); + private static final Iterable filterWordRegexList = new ArrayList() { + { + // one on one + add(RegExpUtility.getSafeRegExp(EnglishDateTime.OneOnOneRegex)); + + // (the)? (day|week|month|year) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.SingleAmbiguousTermsRegex)); + } + }; + + public final Iterable getFilterWordRegexList() { + return filterWordRegexList; + } + + public final StringMatcher getSuperfluousWordMatcher() { + return SuperfluousWordMatcher; + } + + private IDateTimeExtractor setExtractor; + + public final IDateTimeExtractor getSetExtractor() { + return setExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateTimeExtractor holidayExtractor; + + public final IDateTimeExtractor getHolidayExtractor() { + return holidayExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeZoneExtractor timeZoneExtractor; + + public final IDateTimeZoneExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + private IDateTimeListExtractor dateTimeAltExtractor; + + public final IDateTimeListExtractor getDateTimeAltExtractor() { + return dateTimeAltExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public EnglishMergedExtractorConfiguration(DateTimeOptions options) { + super(options); + + setExtractor = new BaseSetExtractor(new EnglishSetExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + holidayExtractor = new BaseHolidayExtractor(new EnglishHolidayExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + dateTimeAltExtractor = new BaseDateTimeAltExtractor(new EnglishDateTimeAltExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new EnglishDateTimePeriodExtractorConfiguration(options)); + integerExtractor = IntegerExtractor.getInstance(); + + ambiguityFiltersDict = EnglishDateTime.AmbiguityFiltersDict.entrySet().stream().map(pair -> { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + return new Pair(key, val); + }).collect(Collectors.toList()); + + if (!this.getOptions().match(DateTimeOptions.EnablePreview)) { + getSuperfluousWordMatcher().init(EnglishDateTime.SuperfluousWordList); + } + } + + public final Pattern getAfterRegex() { + return AfterRegex; + } + + public final Pattern getSinceRegex() { + return SinceRegex; + } + + + public final Pattern getAroundRegex() { + return AroundRegex; + } + + public final Pattern getBeforeRegex() { + return BeforeRegex; + } + + public final Pattern getFromToRegex() { + return FromToRegex; + } + + public final Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public final Pattern getNumberEndingPattern() { + return NumberEndingPattern; + } + + public final Pattern getPrepositionSuffixRegex() { + return PrepositionSuffixRegex; + } + + public final Pattern getAmbiguousRangeModifierPrefix() { + return AmbiguousRangeModifierPrefix; + } + + public final Pattern getPotentialAmbiguousRangeRegex() { + return FromToRegex; + } + + public final Pattern getSingleAmbiguousMonthRegex() { + return SingleAmbiguousMonthRegex; + } + + public final Pattern getUnspecificDatePeriodRegex() { + return UnspecificDatePeriodRegex; + } + + public final Iterable> getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java new file mode 100644 index 000000000..6879dc559 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java @@ -0,0 +1,120 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishSetExtractorConfiguration extends BaseOptionsConfiguration implements ISetExtractorConfiguration { + + public static final Pattern SetLastRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SetLastRegex); + public static final Pattern EachDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EachDayRegex); + public static final Pattern SetEachRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SetEachRegex); + public static final Pattern PeriodicRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodicRegex); + public static final Pattern EachUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EachUnitRegex); + public static final Pattern SetUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationUnitRegex); + public static final Pattern EachPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EachPrefixRegex); + public static final Pattern SetWeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SetWeekDayRegex); + + public EnglishSetExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishSetExtractorConfiguration(DateTimeOptions options) { + super(options); + + timeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + dateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new EnglishDateTimePeriodExtractorConfiguration(options)); + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final Pattern getLastRegex() { + return SetLastRegex; + } + + public final Pattern getBeforeEachDayRegex() { + return null; + } + + public final Pattern getEachDayRegex() { + return EachDayRegex; + } + + public final Pattern getSetEachRegex() { + return SetEachRegex; + } + + public final Pattern getPeriodicRegex() { + return PeriodicRegex; + } + + public final Pattern getEachUnitRegex() { + return EachUnitRegex; + } + + public final Pattern getSetWeekDayRegex() { + return SetWeekDayRegex; + } + + public final Pattern getEachPrefixRegex() { + return EachPrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java new file mode 100644 index 000000000..d4d1c4a61 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java @@ -0,0 +1,140 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishTimeExtractorConfiguration extends BaseOptionsConfiguration implements ITimeExtractorConfiguration { + + // part 1: smallest component + // -------------------------------------- + public static final Pattern DescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DescRegex); + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.HourNumRegex); + public static final Pattern MinuteNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MinuteNumRegex); + + // part 2: middle level component + // -------------------------------------- + // handle "... o'clock" + public static final Pattern OclockRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OclockRegex); + + // handle "... afternoon" + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmRegex); + + // handle "... in the morning" + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmRegex); + + // handle "half past ..." "a quarter to ..." + // rename 'min' group to 'deltamin' + public static final Pattern LessThanOneHour = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanOneHour); + + // handle "six thirty", "six twenty one" + public static final Pattern BasicTime = RegExpUtility.getSafeRegExp(EnglishDateTime.BasicTime); + public static final Pattern TimePrefix = RegExpUtility.getSafeRegExp(EnglishDateTime.TimePrefix); + public static final Pattern TimeSuffix = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeSuffix); + public static final Pattern WrittenTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WrittenTimeRegex); + + // handle special time such as 'at midnight', 'midnight', 'midday' + public static final Pattern MiddayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MiddayRegex); + public static final Pattern MidTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidTimeRegex); + public static final Pattern MidnightRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidnightRegex); + public static final Pattern MidmorningRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidmorningRegex); + public static final Pattern MidafternoonRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidafternoonRegex); + + // part 3: regex for time + // -------------------------------------- + // handle "at four" "at 3" + public static final Pattern AtRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AtRegex); + public static final Pattern IshRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.IshRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern ConnectNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ConnectNumRegex); + public static final Pattern TimeBeforeAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeBeforeAfterRegex); + + public static final Iterable TimeRegexList = new ArrayList() { + { + // (three min past)? seven|7|(senven thirty) pm + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex1)); + + // (three min past)? 3:00(:00)? (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex2)); + + // (three min past)? 3.00 (pm) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex3)); + + // (three min past) (five thirty|seven|7|7:00(:00)?) (pm)? (in the night) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex4)); + + // (three min past) (five thirty|seven|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex5)); + + // (five thirty|seven|7|7:00(:00)?) (pm)? (in the night) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex6)); + + // (in the night) at (five thirty|seven|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex7)); + + // (in the night) (five thirty|seven|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex8)); + + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex9)); + + // (three min past)? 3h00 (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex10)); + + // at 2.30, "at" prefix is required here + // 3.30pm, "am/pm" suffix is required here + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex11)); + + // 340pm + add(ConnectNumRegex); + } + }; + + public final Iterable getTimeRegexList() { + return TimeRegexList; + } + + public final Pattern getAtRegex() { + return AtRegex; + } + + public final Pattern getIshRegex() { + return IshRegex; + } + + public final Pattern getTimeBeforeAfterRegex() { + return TimeBeforeAfterRegex; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor timeZoneExtractor; + + public final IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + + public EnglishTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: + //ORIGINAL LINE: public EnglishTimeExtractorConfiguration(DateTimeOptions options = DateTimeOptions.None) + public EnglishTimeExtractorConfiguration(DateTimeOptions options) { + super(options); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..bdf1e1e50 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java @@ -0,0 +1,132 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements ITimePeriodExtractorConfiguration { + + private String tokenBeforeDate; + + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmRegex); + public static final Pattern HourRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.HourRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TillRegex); + public static final Pattern PeriodDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DescRegex); + public static final Pattern PureNumFromTo = RegExpUtility.getSafeRegExp(EnglishDateTime.PureNumFromTo); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfDayRegex); + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrepositionRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeFollowedUnit); + public static final Pattern PureNumBetweenAnd = RegExpUtility.getSafeRegExp(EnglishDateTime.PureNumBetweenAnd); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.GeneralEndingRegex); + public static final Pattern PeriodHourNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodHourNumRegex); + public static final Pattern SpecificTimeFromTo = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeFromTo); + public static final Pattern SpecificTimeBetweenAnd = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeBetweenAnd); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeNumberCombinedWithUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeNumberCombinedWithUnit); + + public EnglishTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + singleTimeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + integerExtractor = IntegerExtractor.getInstance(); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + } + + private IDateTimeUtilityConfiguration utilityConfiguration; + + public final IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + private IDateTimeExtractor singleTimeExtractor; + + public final IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + private final IDateTimeExtractor timeZoneExtractor; + + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + + public Iterable getSimpleCasesRegex() { + return getSimpleCasesRegex; + } + + public final Iterable getSimpleCasesRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + add(SpecificTimeFromTo); + add(SpecificTimeBetweenAnd); + } + }; + + public final Pattern getTillRegex() { + return TillRegex; + } + + public final Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + public final Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + public final ResultIndex getFromTokenIndex(String input) { + ResultIndex result = new ResultIndex(false, -1); + if (input.endsWith("from")) { + result = new ResultIndex(true, input.lastIndexOf("from")); + } + + return result; + } + + public final ResultIndex getBetweenTokenIndex(String input) { + ResultIndex result = new ResultIndex(false, -1); + if (input.endsWith("between")) { + result = new ResultIndex(true, input.lastIndexOf("between")); + } + + return result; + } + + public final boolean hasConnectorToken(String input) { + return input.equals("and"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..71cbb7aff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java @@ -0,0 +1,93 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishTimeZone; +import com.microsoft.recognizers.text.matcher.MatchStrategy; +import com.microsoft.recognizers.text.matcher.NumberWithUnitTokenizer; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class EnglishTimeZoneExtractorConfiguration extends BaseOptionsConfiguration implements ITimeZoneExtractorConfiguration { + + // These regexes do need to be case insensitive for them to work correctly + public static final Pattern DirectUtcRegex = RegExpUtility.getSafeRegExp(EnglishTimeZone.DirectUtcRegex, Pattern.CASE_INSENSITIVE); + public static final List AbbreviationsList = EnglishTimeZone.AbbreviationsList; + public static final List FullNameList = EnglishTimeZone.FullNameList; + public static final Pattern LocationTimeSuffixRegex = RegExpUtility.getSafeRegExp(EnglishTimeZone.LocationTimeSuffixRegex, Pattern.CASE_INSENSITIVE); + public static final StringMatcher LocationMatcher = new StringMatcher(); + public static final StringMatcher TimeZoneMatcher = buildMatcherFromLists(AbbreviationsList, FullNameList); + + public static final List AmbiguousTimezoneList = EnglishTimeZone.AmbiguousTimezoneList; + + public EnglishTimeZoneExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishTimeZoneExtractorConfiguration(DateTimeOptions options) { + + super(options); + + if (options.match(DateTimeOptions.EnablePreview)) { + LocationMatcher.init( + EnglishTimeZone.MajorLocations.stream() + .map(o -> QueryProcessor.removeDiacritics(o.toLowerCase())) + .collect(Collectors.toCollection(ArrayList::new))); + } + } + + protected static StringMatcher buildMatcherFromLists(List...collections) { + StringMatcher matcher = new StringMatcher(MatchStrategy.TrieTree, new NumberWithUnitTokenizer()); + List matcherList = new ArrayList(); + + for (List collection : collections) { + for (String item : collection) { + matcherList.add(item.toLowerCase()); + } + } + + matcherList.stream().forEach( + item -> { + if (!matcherList.contains(item)) { + matcherList.add(item); + } + } + ); + + matcher.init(matcherList); + + return matcher; + } + + @Override + public Pattern getDirectUtcRegex() { + return DirectUtcRegex; + } + + @Override + public Pattern getLocationTimeSuffixRegex() { + return LocationTimeSuffixRegex; + } + + @Override + public StringMatcher getLocationMatcher() { + return LocationMatcher; + } + + @Override + public StringMatcher getTimeZoneMatcher() { + return TimeZoneMatcher; + } + + @Override + public List getAmbiguousTimezoneList() { + return AmbiguousTimezoneList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..c4a219fec --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java @@ -0,0 +1,290 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDatePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeAltParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDurationParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.BaseDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; + +public class EnglishCommonDateTimeParserConfiguration extends BaseDateParserConfiguration implements ICommonDateTimeParserConfiguration { + + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfWeekMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap numbers; + private final ImmutableMap doubleNumbers; + private final ImmutableMap writtenDecades; + private final ImmutableMap specialDecadeCases; + + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser datePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser dateTimeAltParser; + private final IDateTimeParser timeZoneParser; + + public EnglishCommonDateTimeParserConfiguration(DateTimeOptions options) { + + super(options); + + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + + unitMap = EnglishDateTime.UnitMap; + unitValueMap = EnglishDateTime.UnitValueMap; + seasonMap = EnglishDateTime.SeasonMap; + specialYearPrefixesMap = EnglishDateTime.SpecialYearPrefixesMap; + cardinalMap = EnglishDateTime.CardinalMap; + dayOfWeekMap = EnglishDateTime.DayOfWeek; + dayOfMonth = ImmutableMap.builder().putAll(super.getDayOfMonth()).putAll(EnglishDateTime.DayOfMonth).build(); + monthOfYear = EnglishDateTime.MonthOfYear; + numbers = EnglishDateTime.Numbers; + doubleNumbers = EnglishDateTime.DoubleNumbers; + writtenDecades = EnglishDateTime.WrittenDecades; + specialDecadeCases = EnglishDateTime.SpecialDecadeCases; + + cardinalExtractor = CardinalExtractor.getInstance(); + integerExtractor = IntegerExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + dateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new EnglishDateTimePeriodExtractorConfiguration(options)); + + timeZoneParser = new BaseTimeZoneParser(); + durationParser = new BaseDurationParser(new EnglishDurationParserConfiguration(this)); + dateParser = new BaseDateParser(new EnglishDateParserConfiguration(this)); + timeParser = new TimeParser(new EnglishTimeParserConfiguration(this)); + dateTimeParser = new BaseDateTimeParser(new EnglishDateTimeParserConfiguration(this)); + datePeriodParser = new BaseDatePeriodParser(new EnglishDatePeriodParserConfiguration(this)); + timePeriodParser = new BaseTimePeriodParser(new EnglishTimePeriodParserConfiguration(this)); + dateTimePeriodParser = new BaseDateTimePeriodParser(new EnglishDateTimePeriodParserConfiguration(this)); + dateTimeAltParser = new BaseDateTimeAltParser(new EnglishDateTimeAltParserConfiguration(this)); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + @Override + public IDateTimeParser getDateTimeAltParser() { + return dateTimeAltParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeekMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java new file mode 100644 index 000000000..472b5861a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java @@ -0,0 +1,351 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishDateParserConfiguration extends BaseOptionsConfiguration implements IDateParserConfiguration { + + public EnglishDateParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + dateTokenPrefix = EnglishDateTime.DateTokenPrefix; + + integerExtractor = config.getIntegerExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + + dateRegexes = Collections.unmodifiableList(EnglishDateExtractorConfiguration.DateRegexList); + onRegex = EnglishDateExtractorConfiguration.OnRegex; + specialDayRegex = EnglishDateExtractorConfiguration.SpecialDayRegex; + specialDayWithNumRegex = EnglishDateExtractorConfiguration.SpecialDayWithNumRegex; + nextRegex = EnglishDateExtractorConfiguration.NextDateRegex; + thisRegex = EnglishDateExtractorConfiguration.ThisRegex; + lastRegex = EnglishDateExtractorConfiguration.LastDateRegex; + unitRegex = EnglishDateExtractorConfiguration.DateUnitRegex; + weekDayRegex = EnglishDateExtractorConfiguration.WeekDayRegex; + monthRegex = EnglishDateExtractorConfiguration.MonthRegex; + weekDayOfMonthRegex = EnglishDateExtractorConfiguration.WeekDayOfMonthRegex; + forTheRegex = EnglishDateExtractorConfiguration.ForTheRegex; + weekDayAndDayOfMonthRegex = EnglishDateExtractorConfiguration.WeekDayAndDayOfMonthRegex; + relativeMonthRegex = EnglishDateExtractorConfiguration.RelativeMonthRegex; + strictRelativeRegex = EnglishDateExtractorConfiguration.StrictRelativeRegex; + relativeWeekDayRegex = EnglishDateExtractorConfiguration.RelativeWeekDayRegex; + + yearSuffix = EnglishDateExtractorConfiguration.YearSuffix; + unitMap = config.getUnitMap(); + dayOfMonth = config.getDayOfMonth(); + dayOfWeek = config.getDayOfWeek(); + monthOfYear = config.getMonthOfYear(); + cardinalMap = config.getCardinalMap(); + utilityConfiguration = config.getUtilityConfiguration(); + + relativeDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeDayRegex); + nextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + sameDayTerms = Collections.unmodifiableList(EnglishDateTime.SameDayTerms); + plusOneDayTerms = Collections.unmodifiableList(EnglishDateTime.PlusOneDayTerms); + plusTwoDayTerms = Collections.unmodifiableList(EnglishDateTime.PlusTwoDayTerms); + minusOneDayTerms = Collections.unmodifiableList(EnglishDateTime.MinusOneDayTerms); + minusTwoDayTerms = Collections.unmodifiableList(EnglishDateTime.MinusTwoDayTerms); + + } + + private final String dateTokenPrefix; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IExtractor cardinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeParser durationParser; + private final Iterable dateRegexes; + + private final Pattern onRegex; + private final Pattern specialDayRegex; + private final Pattern specialDayWithNumRegex; + private final Pattern nextRegex; + private final Pattern thisRegex; + private final Pattern lastRegex; + private final Pattern unitRegex; + private final Pattern weekDayRegex; + private final Pattern monthRegex; + private final Pattern weekDayOfMonthRegex; + private final Pattern forTheRegex; + private final Pattern weekDayAndDayOfMonthRegex; + private final Pattern relativeMonthRegex; + private final Pattern strictRelativeRegex; + private final Pattern yearSuffix; + private final Pattern relativeWeekDayRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap cardinalMap; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final List sameDayTerms; + private final List plusOneDayTerms; + private final List plusTwoDayTerms; + private final List minusOneDayTerms; + private final List minusTwoDayTerms; + + // The following three regexes only used in this configuration + // They are not used in the base parser, therefore they are not extracted + // If the spanish date parser need the same regexes, they should be extracted + private final Pattern relativeDayRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + + @Override + public String getDateTokenPrefix() { + return dateTokenPrefix; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Iterable getDateRegexes() { + return dateRegexes; + } + + @Override + public Pattern getOnRegex() { + return onRegex; + } + + @Override + public Pattern getSpecialDayRegex() { + return specialDayRegex; + } + + @Override + public Pattern getSpecialDayWithNumRegex() { + return specialDayWithNumRegex; + } + + @Override + public Pattern getNextRegex() { + return nextRegex; + } + + @Override + public Pattern getThisRegex() { + return thisRegex; + } + + @Override + public Pattern getLastRegex() { + return lastRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getMonthRegex() { + return monthRegex; + } + + @Override + public Pattern getWeekDayOfMonthRegex() { + return weekDayOfMonthRegex; + } + + @Override + public Pattern getForTheRegex() { + return forTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return weekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return relativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return strictRelativeRegex; + } + + @Override + public Pattern getYearSuffix() { + return yearSuffix; + } + + @Override + public Pattern getRelativeWeekDayRegex() { + return relativeWeekDayRegex; + } + + @Override + public Pattern getRelativeDayRegex() { + return relativeDayRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public List getSameDayTerms() { + return sameDayTerms; + } + + @Override + public List getPlusOneDayTerms() { + return plusOneDayTerms; + } + + @Override + public List getMinusOneDayTerms() { + return minusOneDayTerms; + } + + @Override + public List getPlusTwoDayTerms() { + return plusTwoDayTerms; + } + + @Override + public List getMinusTwoDayTerms() { + return minusTwoDayTerms; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Integer getSwiftMonthOrYear(String text) { + return getSwift(text); + } + + private Integer getSwift(String text) { + + String trimmedText = text.trim().toLowerCase(); + Integer swift = 0; + + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchPast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + + if (matchNext.isPresent()) { + swift = 1; + } else if (matchPast.isPresent()) { + swift = -1; + } + + return swift; + } + + @Override + public Boolean isCardinalLast(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("last"); + } + + @Override + public String normalize(String text) { + return text; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java new file mode 100644 index 000000000..ff24cd50d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java @@ -0,0 +1,575 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class EnglishDatePeriodParserConfiguration extends BaseOptionsConfiguration implements IDatePeriodParserConfiguration { + + public EnglishDatePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + + cardinalExtractor = config.getCardinalExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + durationExtractor = config.getDurationExtractor(); + durationParser = config.getDurationParser(); + dateParser = config.getDateParser(); + + monthFrontBetweenRegex = EnglishDatePeriodExtractorConfiguration.MonthFrontBetweenRegex; + betweenRegex = EnglishDatePeriodExtractorConfiguration.BetweenRegex; + monthFrontSimpleCasesRegex = EnglishDatePeriodExtractorConfiguration.MonthFrontSimpleCasesRegex; + simpleCasesRegex = EnglishDatePeriodExtractorConfiguration.SimpleCasesRegex; + oneWordPeriodRegex = EnglishDatePeriodExtractorConfiguration.OneWordPeriodRegex; + monthWithYear = EnglishDatePeriodExtractorConfiguration.MonthWithYear; + monthNumWithYear = EnglishDatePeriodExtractorConfiguration.MonthNumWithYear; + yearRegex = EnglishDatePeriodExtractorConfiguration.YearRegex; + pastRegex = EnglishDatePeriodExtractorConfiguration.PreviousPrefixRegex; + futureRegex = EnglishDatePeriodExtractorConfiguration.NextPrefixRegex; + futureSuffixRegex = EnglishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnit = EnglishDurationExtractorConfiguration.NumberCombinedWithDurationUnit; + weekOfMonthRegex = EnglishDatePeriodExtractorConfiguration.WeekOfMonthRegex; + weekOfYearRegex = EnglishDatePeriodExtractorConfiguration.WeekOfYearRegex; + quarterRegex = EnglishDatePeriodExtractorConfiguration.QuarterRegex; + quarterRegexYearFront = EnglishDatePeriodExtractorConfiguration.QuarterRegexYearFront; + allHalfYearRegex = EnglishDatePeriodExtractorConfiguration.AllHalfYearRegex; + seasonRegex = EnglishDatePeriodExtractorConfiguration.SeasonRegex; + whichWeekRegex = EnglishDatePeriodExtractorConfiguration.WhichWeekRegex; + weekOfRegex = EnglishDatePeriodExtractorConfiguration.WeekOfRegex; + monthOfRegex = EnglishDatePeriodExtractorConfiguration.MonthOfRegex; + restOfDateRegex = EnglishDatePeriodExtractorConfiguration.RestOfDateRegex; + laterEarlyPeriodRegex = EnglishDatePeriodExtractorConfiguration.LaterEarlyPeriodRegex; + weekWithWeekDayRangeRegex = EnglishDatePeriodExtractorConfiguration.WeekWithWeekDayRangeRegex; + yearPlusNumberRegex = EnglishDatePeriodExtractorConfiguration.YearPlusNumberRegex; + decadeWithCenturyRegex = EnglishDatePeriodExtractorConfiguration.DecadeWithCenturyRegex; + yearPeriodRegex = EnglishDatePeriodExtractorConfiguration.YearPeriodRegex; + complexDatePeriodRegex = EnglishDatePeriodExtractorConfiguration.ComplexDatePeriodRegex; + relativeDecadeRegex = EnglishDatePeriodExtractorConfiguration.RelativeDecadeRegex; + inConnectorRegex = config.getUtilityConfiguration().getInConnectorRegex(); + withinNextPrefixRegex = EnglishDatePeriodExtractorConfiguration.WithinNextPrefixRegex; + referenceDatePeriodRegex = EnglishDatePeriodExtractorConfiguration.ReferenceDatePeriodRegex; + agoRegex = EnglishDatePeriodExtractorConfiguration.AgoRegex; + laterRegex = EnglishDatePeriodExtractorConfiguration.LaterRegex; + lessThanRegex = EnglishDatePeriodExtractorConfiguration.LessThanRegex; + moreThanRegex = EnglishDatePeriodExtractorConfiguration.MoreThanRegex; + centurySuffixRegex = EnglishDatePeriodExtractorConfiguration.CenturySuffixRegex; + relativeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeRegex); + unspecificEndOfRangeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.UnspecificEndOfRangeRegex); + nowRegex = EnglishDatePeriodExtractorConfiguration.NowRegex; + + unitMap = config.getUnitMap(); + cardinalMap = config.getCardinalMap(); + dayOfMonth = config.getDayOfMonth(); + monthOfYear = config.getMonthOfYear(); + seasonMap = config.getSeasonMap(); + specialYearPrefixesMap = config.getSpecialYearPrefixesMap(); + writtenDecades = config.getWrittenDecades(); + numbers = config.getNumbers(); + specialDecadeCases = config.getSpecialDecadeCases(); + + nextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + thisPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ThisPrefixRegex); + afterNextSuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfterNextSuffixRegex); + } + + private final String tokenBeforeDate; + + // InternalParsers + + private final IDateExtractor dateExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor integerExtractor; + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser durationParser; + + // Regex + + private final Pattern monthFrontBetweenRegex; + private final Pattern betweenRegex; + private final Pattern monthFrontSimpleCasesRegex; + private final Pattern simpleCasesRegex; + private final Pattern oneWordPeriodRegex; + private final Pattern monthWithYear; + private final Pattern monthNumWithYear; + private final Pattern yearRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnit; + private final Pattern weekOfMonthRegex; + private final Pattern weekOfYearRegex; + private final Pattern quarterRegex; + private final Pattern quarterRegexYearFront; + private final Pattern allHalfYearRegex; + private final Pattern seasonRegex; + private final Pattern whichWeekRegex; + private final Pattern weekOfRegex; + private final Pattern monthOfRegex; + private final Pattern inConnectorRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern restOfDateRegex; + private final Pattern laterEarlyPeriodRegex; + private final Pattern weekWithWeekDayRangeRegex; + private final Pattern yearPlusNumberRegex; + private final Pattern decadeWithCenturyRegex; + private final Pattern yearPeriodRegex; + private final Pattern complexDatePeriodRegex; + private final Pattern relativeDecadeRegex; + private final Pattern referenceDatePeriodRegex; + private final Pattern agoRegex; + private final Pattern laterRegex; + private final Pattern lessThanRegex; + private final Pattern moreThanRegex; + private final Pattern centurySuffixRegex; + private final Pattern relativeRegex; + private final Pattern unspecificEndOfRangeRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + private final Pattern thisPrefixRegex; + private final Pattern afterNextSuffixRegex; + private final Pattern nowRegex; + + // Dictionaries + private final ImmutableMap unitMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap writtenDecades; + private final ImmutableMap numbers; + private final ImmutableMap specialDecadeCases; + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public Pattern getMonthFrontBetweenRegex() { + return monthFrontBetweenRegex; + } + + @Override + public Pattern getBetweenRegex() { + return betweenRegex; + } + + @Override + public Pattern getMonthFrontSimpleCasesRegex() { + return monthFrontSimpleCasesRegex; + } + + @Override + public Pattern getSimpleCasesRegex() { + return simpleCasesRegex; + } + + @Override + public Pattern getOneWordPeriodRegex() { + return oneWordPeriodRegex; + } + + @Override + public Pattern getMonthWithYear() { + return monthWithYear; + } + + @Override + public Pattern getMonthNumWithYear() { + return monthNumWithYear; + } + + @Override + public Pattern getYearRegex() { + return yearRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getWeekOfMonthRegex() { + return weekOfMonthRegex; + } + + @Override + public Pattern getWeekOfYearRegex() { + return weekOfYearRegex; + } + + @Override + public Pattern getQuarterRegex() { + return quarterRegex; + } + + @Override + public Pattern getQuarterRegexYearFront() { + return quarterRegexYearFront; + } + + @Override + public Pattern getAllHalfYearRegex() { + return allHalfYearRegex; + } + + @Override + public Pattern getSeasonRegex() { + return seasonRegex; + } + + @Override + public Pattern getWhichWeekRegex() { + return whichWeekRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return weekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return monthOfRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return inConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return thisPrefixRegex; + } + + @Override + public Pattern getRestOfDateRegex() { + return restOfDateRegex; + } + + @Override + public Pattern getLaterEarlyPeriodRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public Pattern getWeekWithWeekDayRangeRegex() { + return weekWithWeekDayRangeRegex; + } + + @Override + public Pattern getYearPlusNumberRegex() { + return yearPlusNumberRegex; + } + + @Override + public Pattern getDecadeWithCenturyRegex() { + return decadeWithCenturyRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return yearPeriodRegex; + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return complexDatePeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return relativeDecadeRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return referenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return agoRegex; + } + + @Override + public Pattern getLaterRegex() { + return laterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return lessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return moreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return centurySuffixRegex; + } + + @Override + public Pattern getRelativeRegex() { + return relativeRegex; + } + + @Override + public Pattern getUnspecificEndOfRangeRegex() { + return unspecificEndOfRangeRegex; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public int getSwiftDayOrMonth(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchPast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNext.isPresent()) { + swift = 1; + } else if (matchPast.isPresent()) { + swift = -1; + } + + return swift; + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = -10; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchPast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + Optional matchThisPresent = Arrays.stream(RegExpUtility.getMatches(thisPrefixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNext.isPresent()) { + swift = 1; + } else if (matchPast.isPresent()) { + swift = -1; + } else if (matchThisPresent.isPresent()) { + swift = 0; + } + + return swift; + } + + @Override + public boolean isFuture(String text) { + String trimmedText = text.trim().toLowerCase(); + return (trimmedText.startsWith("this") || trimmedText.startsWith("next")); + } + + @Override + public boolean isLastCardinal(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("last"); + } + + @Override + public boolean isMonthOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + return trimmedText.endsWith("month") || trimmedText.contains(" month ") && matchAfterNext.isPresent(); + } + + @Override + public boolean isMonthToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("month to date"); + } + + @Override + public boolean isWeekend(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + return trimmedText.endsWith("weekend") || trimmedText.contains(" weekend ") && matchAfterNext.isPresent(); + } + + @Override + public boolean isWeekOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + return trimmedText.endsWith("week") || trimmedText.contains(" week ") && matchAfterNext.isPresent(); + } + + @Override + public boolean isYearOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + return EnglishDateTime.YearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + (getYearTermsPadded().anyMatch(o -> trimmedText.contains(o)) && RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText).length > 0) || + (EnglishDateTime.GenericYearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) && RegExpUtility.getMatches(unspecificEndOfRangeRegex, trimmedText).length > 0); + } + + @Override + public boolean isYearToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("year to date"); + } + + private Stream getYearTermsPadded() { + return EnglishDateTime.YearTerms.stream().map(i -> String.format(" %s ", i)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..158464df9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; + +public class EnglishDateTimeAltParserConfiguration implements IDateTimeAltParserConfiguration { + + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser datePeriodParser; + + public EnglishDateTimeAltParserConfiguration(ICommonDateTimeParserConfiguration config) { + dateTimeParser = config.getDateTimeParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + datePeriodParser = config.getDatePeriodParser(); + } + + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public IDateTimeParser getDateParser() { + return dateParser; + } + + public IDateTimeParser getTimeParser() { + return timeParser; + } + + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java new file mode 100644 index 000000000..aa285aa9e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java @@ -0,0 +1,256 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDateTimeParserConfiguration extends BaseOptionsConfiguration implements IDateTimeParserConfiguration { + + private final String tokenBeforeDate; + private final String tokenBeforeTime; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeParser durationParser; + + private final Pattern nowRegex; + private final Pattern amTimeRegex; + private final Pattern pmTimeRegex; + private final Pattern simpleTimeOfTodayAfterRegex; + private final Pattern simpleTimeOfTodayBeforeRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern specificEndOfRegex; + private final Pattern unspecificEndOfRegex; + private final Pattern unitRegex; + private final Pattern dateNumberConnectorRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public EnglishDateTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + tokenBeforeTime = EnglishDateTime.TokenBeforeTime; + + cardinalExtractor = config.getCardinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + durationExtractor = config.getDurationExtractor(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + durationParser = config.getDurationParser(); + + nowRegex = EnglishDateTimeExtractorConfiguration.NowRegex; + + amTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AMTimeRegex); + pmTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PMTimeRegex); + + simpleTimeOfTodayAfterRegex = EnglishDateTimeExtractorConfiguration.SimpleTimeOfTodayAfterRegex; + simpleTimeOfTodayBeforeRegex = EnglishDateTimeExtractorConfiguration.SimpleTimeOfTodayBeforeRegex; + specificTimeOfDayRegex = EnglishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + specificEndOfRegex = EnglishDateTimeExtractorConfiguration.SpecificEndOfRegex; + unspecificEndOfRegex = EnglishDateTimeExtractorConfiguration.UnspecificEndOfRegex; + unitRegex = EnglishTimeExtractorConfiguration.TimeUnitRegex; + dateNumberConnectorRegex = EnglishDateTimeExtractorConfiguration.DateNumberConnectorRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public String getTokenBeforeTime() { + return tokenBeforeTime; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public Pattern getAMTimeRegex() { + return amTimeRegex; + } + + @Override + public Pattern getPMTimeRegex() { + return pmTimeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return simpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return simpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return specificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return unspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return dateNumberConnectorRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public boolean containsAmbiguousToken(String text, String matchedText) { + return false; + } + + @Override + public ResultTimex getMatchedNowTimex(String text) { + + String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.endsWith("now")) { + return new ResultTimex(true, "PRESENT_REF"); + } else if (trimmedText.equals("recently") || trimmedText.equals("previously")) { + return new ResultTimex(true, "PAST_REF"); + } else if (trimmedText.equals("as soon as possible") || trimmedText.equals("asap")) { + return new ResultTimex(true, "FUTURE_REF"); + } + + return new ResultTimex(false, null); + } + + @Override + public int getSwiftDay(String text) { + + String trimmedText = text.trim().toLowerCase(); + + int swift = 0; + if (trimmedText.startsWith("next")) { + swift = 1; + } else if (trimmedText.startsWith("last")) { + swift = -1; + } + + return swift; + } + + @Override + public int getHour(String text, int hour) { + + String trimmedText = text.trim().toLowerCase(); + int result = hour; + + if (trimmedText.endsWith("morning") && hour >= Constants.HalfDayHourCount) { + result -= Constants.HalfDayHourCount; + } else if (!trimmedText.endsWith("morning") && hour < Constants.HalfDayHourCount && !(trimmedText.endsWith("night") && hour < 6)) { + result += Constants.HalfDayHourCount; + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..3e277e865 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java @@ -0,0 +1,337 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDateTimePeriodParserConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodParserConfiguration { + + private final String tokenBeforeDate; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor cardinalExtractor; + + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern timeOfDayRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnitRegex; + private final Pattern unitRegex; + private final Pattern periodTimeOfDayWithDateRegex; + private final Pattern relativeTimeUnitRegex; + private final Pattern restOfDateTimeRegex; + private final Pattern amDescRegex; + private final Pattern pmDescRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern prefixDayRegex; + private final Pattern beforeRegex; + private final Pattern afterRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + + public static final Pattern MorningStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MorningStartEndRegex); + public static final Pattern AfternoonStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfternoonStartEndRegex); + public static final Pattern EveningStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EveningStartEndRegex); + public static final Pattern NightStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NightStartEndRegex); + + public EnglishDateTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + numberParser = config.getNumberParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + timePeriodParser = config.getTimePeriodParser(); + durationParser = config.getDurationParser(); + dateTimeParser = config.getDateTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + + pureNumberFromToRegex = EnglishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = EnglishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeOfDayRegex = EnglishDateTimePeriodExtractorConfiguration.PeriodSpecificTimeOfDayRegex; + timeOfDayRegex = EnglishDateTimeExtractorConfiguration.TimeOfDayRegex; + pastRegex = EnglishDatePeriodExtractorConfiguration.PreviousPrefixRegex; + futureRegex = EnglishDatePeriodExtractorConfiguration.NextPrefixRegex; + futureSuffixRegex = EnglishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnitRegex = EnglishDateTimePeriodExtractorConfiguration.TimeNumberCombinedWithUnit; + unitRegex = EnglishTimePeriodExtractorConfiguration.TimeUnitRegex; + periodTimeOfDayWithDateRegex = EnglishDateTimePeriodExtractorConfiguration.PeriodTimeOfDayWithDateRegex; + relativeTimeUnitRegex = EnglishDateTimePeriodExtractorConfiguration.RelativeTimeUnitRegex; + restOfDateTimeRegex = EnglishDateTimePeriodExtractorConfiguration.RestOfDateTimeRegex; + amDescRegex = EnglishDateTimePeriodExtractorConfiguration.AmDescRegex; + pmDescRegex = EnglishDateTimePeriodExtractorConfiguration.PmDescRegex; + withinNextPrefixRegex = EnglishDateTimePeriodExtractorConfiguration.WithinNextPrefixRegex; + prefixDayRegex = EnglishDateTimePeriodExtractorConfiguration.PrefixDayRegex; + beforeRegex = EnglishDateTimePeriodExtractorConfiguration.BeforeRegex; + afterRegex = EnglishDateTimePeriodExtractorConfiguration.AfterRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnitRegex() { + return numberCombinedWithUnitRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return periodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return relativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return restOfDateTimeRegex; + } + + @Override + public Pattern getAmDescRegex() { + return amDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return pmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return prefixDayRegex; + } + + @Override + public Pattern getBeforeRegex() { + return beforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return afterRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + private boolean checkRegex(Pattern regex, String input) { + return RegExpUtility.getMatches(regex, input).length > 0; + } + + @Override + public MatchedTimeRangeResult getMatchedTimeRange(String text, String timeStr, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(); + beginHour = 0; + endHour = 0; + endMin = 0; + timeStr = null; + boolean result = false; + + if (checkRegex(MorningStartEndRegex, trimmedText)) { + timeStr = "TMO"; + beginHour = 8; + endHour = Constants.HalfDayHourCount; + result = true; + } else if (checkRegex(AfternoonStartEndRegex, trimmedText)) { + timeStr = "TAF"; + beginHour = Constants.HalfDayHourCount; + endHour = 16; + result = true; + } else if (checkRegex(EveningStartEndRegex, trimmedText)) { + timeStr = "TEV"; + beginHour = 16; + endHour = 20; + result = true; + } else if (checkRegex(NightStartEndRegex, trimmedText)) { + timeStr = "TNI"; + beginHour = 20; + endHour = 23; + endMin = 59; + result = true; + } else { + timeStr = null; + } + + return new MatchedTimeRangeResult(result, timeStr, beginHour, endHour, endMin); + } + + @Override + public int getSwiftPrefix(String text) { + + String trimmedText = text.trim().toLowerCase(); + + int swift = 0; + if (trimmedText.startsWith("next")) { + swift = 1; + } else if (trimmedText.startsWith("last")) { + swift = -1; + } + + return swift; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java new file mode 100644 index 000000000..5a453bd75 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java @@ -0,0 +1,145 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; + +import java.util.regex.Pattern; + +public class EnglishDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { + + private final IExtractor cardinalExtractor; + private final IExtractor durationExtractor; + private final IParser numberParser; + + private final Pattern numberCombinedWithUnit; + private final Pattern anUnitRegex; + private final Pattern duringRegex; + private final Pattern allDateUnitRegex; + private final Pattern halfDateUnitRegex; + private final Pattern suffixAndRegex; + private final Pattern followedUnit; + private final Pattern conjunctionRegex; + private final Pattern inexactNumberRegex; + private final Pattern inexactNumberUnitRegex; + private final Pattern durationUnitRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap doubleNumbers; + + public EnglishDurationParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(), false); + numberCombinedWithUnit = EnglishDurationExtractorConfiguration.NumberCombinedWithDurationUnit; + + anUnitRegex = EnglishDurationExtractorConfiguration.AnUnitRegex; + duringRegex = EnglishDurationExtractorConfiguration.DuringRegex; + allDateUnitRegex = EnglishDurationExtractorConfiguration.AllRegex; + halfDateUnitRegex = EnglishDurationExtractorConfiguration.HalfRegex; + suffixAndRegex = EnglishDurationExtractorConfiguration.SuffixAndRegex; + followedUnit = EnglishDurationExtractorConfiguration.DurationFollowedUnit; + conjunctionRegex = EnglishDurationExtractorConfiguration.ConjunctionRegex; + inexactNumberRegex = EnglishDurationExtractorConfiguration.InexactNumberRegex; + inexactNumberUnitRegex = EnglishDurationExtractorConfiguration.InexactNumberUnitRegex; + durationUnitRegex = EnglishDurationExtractorConfiguration.DurationUnitRegex; + + unitMap = config.getUnitMap(); + unitValueMap = config.getUnitValueMap(); + doubleNumbers = config.getDoubleNumbers(); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return anUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return duringRegex; + } + + @Override + public Pattern getAllDateUnitRegex() { + return allDateUnitRegex; + } + + @Override + public Pattern getHalfDateUnitRegex() { + return halfDateUnitRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return suffixAndRegex; + } + + @Override + public Pattern getFollowedUnit() { + return followedUnit; + } + + @Override + public Pattern getConjunctionRegex() { + return conjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return inexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return inexactNumberUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return durationUnitRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java new file mode 100644 index 000000000..bbac800b0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java @@ -0,0 +1,225 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.function.IntFunction; + +public class EnglishHolidayParserConfiguration extends BaseHolidayParserConfiguration { + + public EnglishHolidayParserConfiguration() { + + super(); + + this.setHolidayRegexList(EnglishHolidayExtractorConfiguration.HolidayRegexList); + + HashMap> newMap = new HashMap<>(); + for (Map.Entry entry : EnglishDateTime.HolidayNames.entrySet()) { + if (entry.getValue() instanceof String[]) { + newMap.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + this.setHolidayNames(ImmutableMap.copyOf(newMap)); + } + + @Override + protected HashMap> initHolidayFuncs() { + + HashMap> holidays = new HashMap<>(super.initHolidayFuncs()); + holidays.put("mayday", EnglishHolidayParserConfiguration::mayday); + holidays.put("yuandan", EnglishHolidayParserConfiguration::newYear); + holidays.put("newyear", EnglishHolidayParserConfiguration::newYear); + holidays.put("youthday", EnglishHolidayParserConfiguration::youthDay); + holidays.put("girlsday", EnglishHolidayParserConfiguration::girlsDay); + holidays.put("xmas", EnglishHolidayParserConfiguration::christmasDay); + holidays.put("newyearday", EnglishHolidayParserConfiguration::newYear); + holidays.put("aprilfools", EnglishHolidayParserConfiguration::foolDay); + holidays.put("easterday", EnglishHolidayParserConfiguration::easterDay); + holidays.put("newyearsday", EnglishHolidayParserConfiguration::newYear); + holidays.put("femaleday", EnglishHolidayParserConfiguration::femaleDay); + holidays.put("singleday", EnglishHolidayParserConfiguration::singlesDay); + holidays.put("newyeareve", EnglishHolidayParserConfiguration::newYearEve); + holidays.put("arborday", EnglishHolidayParserConfiguration::treePlantDay); + holidays.put("loverday", EnglishHolidayParserConfiguration::valentinesDay); + holidays.put("christmas", EnglishHolidayParserConfiguration::christmasDay); + holidays.put("teachersday", EnglishHolidayParserConfiguration::teacherDay); + holidays.put("stgeorgeday", EnglishHolidayParserConfiguration::stGeorgeDay); + holidays.put("baptisteday", EnglishHolidayParserConfiguration::baptisteDay); + holidays.put("bastilleday", EnglishHolidayParserConfiguration::bastilleDay); + holidays.put("allsoulsday", EnglishHolidayParserConfiguration::allSoulsDay); + holidays.put("veteransday", EnglishHolidayParserConfiguration::veteransDay); + holidays.put("childrenday", EnglishHolidayParserConfiguration::childrenDay); + holidays.put("maosbirthday", EnglishHolidayParserConfiguration::maoBirthday); + holidays.put("allsaintsday", EnglishHolidayParserConfiguration::halloweenDay); + holidays.put("stpatrickday", EnglishHolidayParserConfiguration::stPatrickDay); + holidays.put("halloweenday", EnglishHolidayParserConfiguration::halloweenDay); + holidays.put("allhallowday", EnglishHolidayParserConfiguration::allHallowDay); + holidays.put("guyfawkesday", EnglishHolidayParserConfiguration::guyFawkesDay); + holidays.put("christmaseve", EnglishHolidayParserConfiguration::christmasEve); + holidays.put("groundhougday", EnglishHolidayParserConfiguration::groundhogDay); + holidays.put("whiteloverday", EnglishHolidayParserConfiguration::whiteLoverDay); + holidays.put("valentinesday", EnglishHolidayParserConfiguration::valentinesDay); + holidays.put("treeplantingday", EnglishHolidayParserConfiguration::treePlantDay); + holidays.put("cincodemayoday", EnglishHolidayParserConfiguration::cincoDeMayoDay); + holidays.put("inaugurationday", EnglishHolidayParserConfiguration::inaugurationDay); + holidays.put("independenceday", EnglishHolidayParserConfiguration::usaIndependenceDay); + holidays.put("usindependenceday", EnglishHolidayParserConfiguration::usaIndependenceDay); + holidays.put("juneteenth", EnglishHolidayParserConfiguration::juneteenth); + + return holidays; + } + + private static LocalDateTime easterDay(int year) { + return DateUtil.minValue(); + } + + private static LocalDateTime mayday(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + private static LocalDateTime newYear(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 1); + } + + private static LocalDateTime foolDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 1); + } + + private static LocalDateTime girlsDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 7); + } + + private static LocalDateTime youthDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 4); + } + + private static LocalDateTime femaleDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 8); + } + + private static LocalDateTime childrenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 1); + } + + private static LocalDateTime teacherDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 9, 10); + } + + private static LocalDateTime groundhogDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 2); + } + + private static LocalDateTime stGeorgeDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 23); + } + + private static LocalDateTime baptisteDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 24); + } + + private static LocalDateTime bastilleDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 14); + } + + private static LocalDateTime allSoulsDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 2); + } + + private static LocalDateTime singlesDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + private static LocalDateTime newYearEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 31); + } + + private static LocalDateTime treePlantDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 12); + } + + private static LocalDateTime stPatrickDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 17); + } + + private static LocalDateTime allHallowDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 1); + } + + private static LocalDateTime guyFawkesDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 5); + } + + private static LocalDateTime veteransDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + private static LocalDateTime maoBirthday(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 26); + } + + private static LocalDateTime valentinesDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 14); + } + + private static LocalDateTime whiteLoverDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 14); + } + + private static LocalDateTime cincoDeMayoDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 5); + } + + private static LocalDateTime halloweenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 10, 31); + } + + private static LocalDateTime christmasEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 24); + } + + private static LocalDateTime christmasDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 25); + } + + private static LocalDateTime inaugurationDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 20); + } + + private static LocalDateTime usaIndependenceDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 4); + } + + private static LocalDateTime juneteenth(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 19); + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = StringUtility.trimStart(StringUtility.trimEnd(text)).toLowerCase(Locale.ROOT); + int swift = -10; + + if (trimmedText.startsWith("next")) { + swift = 1; + } else if (trimmedText.startsWith("last")) { + swift = -1; + } else if (trimmedText.startsWith("this")) { + swift = 0; + } + + return swift; + } + + public String sanitizeHolidayToken(String holiday) { + return holiday.replace(" ", "").replace("'", ""); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java new file mode 100644 index 000000000..b8b43c163 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseSetParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +public class EnglishMergedParserConfiguration extends EnglishCommonDateTimeParserConfiguration implements IMergedParserConfiguration { + + public EnglishMergedParserConfiguration(DateTimeOptions options) { + super(options); + + beforeRegex = EnglishMergedExtractorConfiguration.BeforeRegex; + afterRegex = EnglishMergedExtractorConfiguration.AfterRegex; + sinceRegex = EnglishMergedExtractorConfiguration.SinceRegex; + aroundRegex = EnglishMergedExtractorConfiguration.AroundRegex; + suffixAfterRegex = EnglishMergedExtractorConfiguration.SuffixAfterRegex; + yearRegex = EnglishDatePeriodExtractorConfiguration.YearRegex; + superfluousWordMatcher = EnglishMergedExtractorConfiguration.SuperfluousWordMatcher; + + getParser = new BaseSetParser(new EnglishSetParserConfiguration(this)); + holidayParser = new BaseHolidayParser(new EnglishHolidayParserConfiguration()); + } + + private final Pattern beforeRegex; + private final Pattern afterRegex; + private final Pattern sinceRegex; + private final Pattern aroundRegex; + private final Pattern suffixAfterRegex; + private final Pattern yearRegex; + private final IDateTimeParser getParser; + private final IDateTimeParser holidayParser; + private final StringMatcher superfluousWordMatcher; + + public Pattern getBeforeRegex() { + return beforeRegex; + } + + public Pattern getAfterRegex() { + return afterRegex; + } + + public Pattern getSinceRegex() { + return sinceRegex; + } + + public Pattern getAroundRegex() { + return aroundRegex; + } + + public Pattern getSuffixAfterRegex() { + return suffixAfterRegex; + } + + public Pattern getYearRegex() { + return yearRegex; + } + + public IDateTimeParser getGetParser() { + return getParser; + } + + public IDateTimeParser getHolidayParser() { + return holidayParser; + } + + public StringMatcher getSuperfluousWordMatcher() { + return superfluousWordMatcher; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java new file mode 100644 index 000000000..290cd5a7f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java @@ -0,0 +1,251 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishSetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Locale; +import java.util.regex.Pattern; + +public class EnglishSetParserConfiguration extends BaseOptionsConfiguration implements ISetParserConfiguration { + + private IDateTimeParser timeParser; + + public final IDateTimeParser getTimeParser() { + return timeParser; + } + + private IDateTimeParser dateParser; + + public final IDateTimeParser getDateParser() { + return dateParser; + } + + private ImmutableMap unitMap; + + public final ImmutableMap getUnitMap() { + return unitMap; + } + + private IDateTimeParser dateTimeParser; + + public final IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + private IDateTimeParser durationParser; + + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeParser datePeriodParser; + + public final IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + private IDateTimeParser timePeriodParser; + + public final IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeParser dateTimePeriodParser; + + public final IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + private Pattern eachDayRegex; + + public final Pattern getEachDayRegex() { + return eachDayRegex; + } + + private Pattern setEachRegex; + + public final Pattern getSetEachRegex() { + return setEachRegex; + } + + private Pattern periodicRegex; + + public final Pattern getPeriodicRegex() { + return periodicRegex; + } + + private Pattern eachUnitRegex; + + public final Pattern getEachUnitRegex() { + return eachUnitRegex; + } + + private Pattern setWeekDayRegex; + + public final Pattern getSetWeekDayRegex() { + return setWeekDayRegex; + } + + private Pattern eachPrefixRegex; + + public final Pattern getEachPrefixRegex() { + return eachPrefixRegex; + } + + private static Pattern doubleMultiplierRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.DoubleMultiplierRegex); + + private static Pattern halfMultiplierRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.HalfMultiplierRegex); + + private static Pattern dayTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.DayTypeRegex); + + private static Pattern weekTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.WeekTypeRegex); + + private static Pattern weekendTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.WeekendTypeRegex); + + private static Pattern monthTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.MonthTypeRegex); + + private static Pattern quarterTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.QuarterTypeRegex); + + private static Pattern yearTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.YearTypeRegex); + + public EnglishSetParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + dateExtractor = config.getDateExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + durationExtractor = config.getDurationExtractor(); + datePeriodExtractor = config.getDatePeriodExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + dateTimePeriodExtractor = config.getDateTimePeriodExtractor(); + + unitMap = config.getUnitMap(); + timeParser = config.getTimeParser(); + dateParser = config.getDateParser(); + dateTimeParser = config.getDateTimeParser(); + durationParser = config.getDurationParser(); + datePeriodParser = config.getDatePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + + eachDayRegex = EnglishSetExtractorConfiguration.EachDayRegex; + setEachRegex = EnglishSetExtractorConfiguration.SetEachRegex; + eachUnitRegex = EnglishSetExtractorConfiguration.EachUnitRegex; + periodicRegex = EnglishSetExtractorConfiguration.PeriodicRegex; + eachPrefixRegex = EnglishSetExtractorConfiguration.EachPrefixRegex; + setWeekDayRegex = EnglishSetExtractorConfiguration.SetWeekDayRegex; + } + + public MatchedTimexResult getMatchedDailyTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + float durationLength = 1; // Default value + float multiplier = 1; + String durationType; + + if (trimmedText.equals("daily")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("weekly")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("biweekly")) { + result.setTimex("P2W"); + } else if (trimmedText.equals("monthly")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("quarterly")) { + result.setTimex("P3M"); + } else if (trimmedText.equals("yearly") || trimmedText.equals("annually") || trimmedText.equals("annual")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } + + public MatchedTimexResult getMatchedUnitTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + if (trimmedText.equals("day")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("week")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("month")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("year")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java new file mode 100644 index 000000000..72fab44ab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java @@ -0,0 +1,187 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishTimeParserConfiguration extends BaseOptionsConfiguration implements ITimeParserConfiguration { + + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeParser timeZoneParser; + + private final Pattern atRegex; + private final Iterable timeRegexes; + private final Pattern timeSuffixFull = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeSuffixFull); + private final Pattern lunchRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LunchRegex); + private final Pattern nightRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NightRegex); + + public EnglishTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + timeZoneParser = new BaseTimeZoneParser(); + + atRegex = EnglishTimeExtractorConfiguration.AtRegex; + timeRegexes = EnglishTimeExtractorConfiguration.TimeRegexList; + } + + @Override + public String getTimeTokenPrefix() { + return EnglishDateTime.TimeTokenPrefix; + } + + @Override + public Pattern getAtRegex() { + return atRegex; + } + + @Override + public Iterable getTimeRegexes() { + return timeRegexes; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public PrefixAdjustResult adjustByPrefix(String prefix, int hour, int min, boolean hasMin) { + + int deltaMin; + String trimmedPrefix = prefix.trim().toLowerCase(); + + if (trimmedPrefix.startsWith("half")) { + deltaMin = 30; + } else if (trimmedPrefix.startsWith("a quarter") || trimmedPrefix.startsWith("quarter")) { + deltaMin = 15; + } else if (trimmedPrefix.startsWith("three quarter")) { + deltaMin = 45; + } else { + + Optional match = Arrays.stream(RegExpUtility.getMatches(EnglishTimeExtractorConfiguration.LessThanOneHour, trimmedPrefix)).findFirst(); + String minStr = match.get().getGroup("deltamin").value; + if (!StringUtility.isNullOrWhiteSpace(minStr)) { + deltaMin = Integer.parseInt(minStr); + } else { + minStr = match.get().getGroup("deltaminnum").value; + deltaMin = numbers.getOrDefault(minStr, 0); + } + } + + if (trimmedPrefix.endsWith("to")) { + deltaMin = -deltaMin; + } + + min += deltaMin; + if (min < 0) { + min += 60; + hour -= 1; + } + + hasMin = true; + + return new PrefixAdjustResult(hour, min, hasMin); + } + + @Override + public SuffixAdjustResult adjustBySuffix(String suffix, int hour, int min, boolean hasMin, boolean hasAm, boolean hasPm) { + + String lowerSuffix = suffix.toLowerCase(); + int deltaHour = 0; + ConditionalMatch match = RegexExtension.matchExact(timeSuffixFull, lowerSuffix, true); + if (match.getSuccess()) { + + String oclockStr = match.getMatch().get().getGroup("oclock").value; + if (StringUtility.isNullOrEmpty(oclockStr)) { + + String amStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + if (!StringUtility.isNullOrEmpty(amStr)) { + if (hour >= Constants.HalfDayHourCount) { + deltaHour = -Constants.HalfDayHourCount; + } else { + hasAm = true; + } + + } + + String pmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + if (!StringUtility.isNullOrEmpty(pmStr)) { + if (hour < Constants.HalfDayHourCount) { + deltaHour = Constants.HalfDayHourCount; + } + + if (checkMatch(lunchRegex, pmStr)) { + // for hour >= 10, < 12 + if (hour >= 10 && hour <= Constants.HalfDayHourCount) { + deltaHour = 0; + if (hour == Constants.HalfDayHourCount) { + hasPm = true; + } else { + hasAm = true; + } + + } else { + hasPm = true; + } + + } else if (checkMatch(nightRegex, pmStr)) { + //For hour <= 3 or == 12, we treat it as am, for example 1 in the night (midnight) == 1am + if (hour <= 3 || hour == Constants.HalfDayHourCount) { + if (hour == Constants.HalfDayHourCount) { + hour = 0; + } + + deltaHour = 0; + hasAm = true; + } else { + hasPm = true; + } + + } else { + hasPm = true; + } + } + } + } + + hour = (hour + deltaHour) % 24; + + return new SuffixAdjustResult(hour, min, hasMin, hasAm, hasPm); + } + + private boolean checkMatch(Pattern regex, String input) { + return RegExpUtility.getMatches(regex, input).length > 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java new file mode 100644 index 000000000..0d6d17779 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java @@ -0,0 +1,159 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.TimeOfDayResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; + +import java.util.regex.Pattern; + +public class EnglishTimePeriodParserConfiguration extends BaseOptionsConfiguration implements ITimePeriodParserConfiguration { + + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IExtractor integerExtractor; + private final IDateTimeParser timeZoneParser; + + private final Pattern specificTimeFromToRegex; + private final Pattern specificTimeBetweenAndRegex; + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern timeOfDayRegex; + private final Pattern generalEndingRegex; + private final Pattern tillRegex; + + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final ImmutableMap numbers; + + public EnglishTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + integerExtractor = config.getIntegerExtractor(); + timeParser = config.getTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + + pureNumberFromToRegex = EnglishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = EnglishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeFromToRegex = EnglishTimePeriodExtractorConfiguration.SpecificTimeFromTo; + specificTimeBetweenAndRegex = EnglishTimePeriodExtractorConfiguration.SpecificTimeBetweenAnd; + timeOfDayRegex = EnglishTimePeriodExtractorConfiguration.TimeOfDayRegex; + + generalEndingRegex = EnglishTimePeriodExtractorConfiguration.GeneralEndingRegex; + tillRegex = EnglishTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeFromToRegex() { + return specificTimeFromToRegex; + } + + @Override + public Pattern getSpecificTimeBetweenAndRegex() { + return specificTimeBetweenAndRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return generalEndingRegex; + } + + @Override + public Pattern getTillRegex() { + return tillRegex; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public MatchedTimeRangeResult getMatchedTimexRange(String text, String timex, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(); + if (trimmedText.endsWith("s")) { + trimmedText = trimmedText.substring(0, trimmedText.length() - 1); + } + + beginHour = 0; + endHour = 0; + endMin = 0; + + String timeOfDay = ""; + + if (EnglishDateTime.MorningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Morning; + } else if (EnglishDateTime.AfternoonTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Afternoon; + } else if (EnglishDateTime.EveningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Evening; + } else if (EnglishDateTime.DaytimeTermList.stream().anyMatch(trimmedText::equals)) { + timeOfDay = Constants.Daytime; + } else if (EnglishDateTime.NightTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Night; + } else if (EnglishDateTime.BusinessHourSplitStrings.stream().allMatch(trimmedText::contains)) { + timeOfDay = Constants.BusinessHour; + } else { + timex = null; + return new MatchedTimeRangeResult(false, timex, beginHour, endHour, endMin); + } + + TimeOfDayResolutionResult result = TimexUtility.parseTimeOfDay(timeOfDay); + + return new MatchedTimeRangeResult(true, result.getTimex(), result.getBeginHour(), result.getEndHour(), result.getEndMin()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java new file mode 100644 index 000000000..5c898aecc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java @@ -0,0 +1,63 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Optional; + +public class TimeParser extends BaseTimeParser { + + public TimeParser(ITimeParserConfiguration config) { + super(config); + } + + @Override + protected DateTimeResolutionResult internalParse(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult innerResult = super.internalParse(text, referenceTime); + + if (!innerResult.getSuccess()) { + innerResult = parseIsh(text, referenceTime); + } + + return innerResult; + } + + // parse "noonish", "11-ish" + private DateTimeResolutionResult parseIsh(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String lowerText = text.toLowerCase(); + + ConditionalMatch match = RegexExtension.matchExact(EnglishTimeExtractorConfiguration.IshRegex, text, true); + if (match.getSuccess()) { + String hourStr = match.getMatch().get().getGroup(Constants.HourGroupName).value; + int hour = Constants.HalfDayHourCount; + + if (!StringUtility.isNullOrEmpty(hourStr)) { + hour = Integer.parseInt(hourStr); + } + + result.setTimex(String.format("T%02d", hour)); + LocalDateTime resultTime = DateUtil.safeCreateFromMinValue( + referenceTime.getYear(), + referenceTime.getMonthValue(), + referenceTime.getDayOfMonth(), + hour, 0, 0); + result.setFutureValue(resultTime); + result.setPastValue(resultTime); + result.setSuccess(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java new file mode 100644 index 000000000..b85e88b72 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.datetime.english.utilities; + +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { + + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LaterRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WithinNextPrefixRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmDescRegex); + public static final Pattern AmPmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmPmDescRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern CommonDatePrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.CommonDatePrefixRegex); + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getAmPmDescRegex() { + return AmPmDescRegex; + } + + @Override + public Pattern getCommonDatePrefixRegex() { + return CommonDatePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java new file mode 100644 index 000000000..65d13f941 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java @@ -0,0 +1,108 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.List; + +public abstract class AbstractYearExtractor implements IDateExtractor { + + protected final IDateExtractorConfiguration config; + + public AbstractYearExtractor(IDateExtractorConfiguration config) { + this.config = config; + } + + @Override + public abstract String getExtractorName(); + + @Override + public abstract List extract(String input, LocalDateTime reference); + + @Override + public abstract List extract(String input); + + @Override + public int getYearFromText(Match match) { + + int year = Constants.InvalidYear; + + String yearStr = match.getGroup("year").value; + String writtenYearStr = match.getGroup("fullyear").value; + + if (!StringUtility.isNullOrEmpty(yearStr) && !yearStr.equals(writtenYearStr)) { + + year = Math.round(Double.valueOf(yearStr).floatValue()); + + if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { + year += 1900; + } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { + year += 2000; + } + } else { + + MatchGroup firstTwoYear = match.getGroup("firsttwoyearnum"); + + if (!StringUtility.isNullOrEmpty(firstTwoYear.value)) { + ExtractResult er = new ExtractResult(); + er.setStart(firstTwoYear.index); + er.setLength(firstTwoYear.length); + er.setText(firstTwoYear.value); + + int firstTwoYearNum = Math.round(Double.valueOf((double)config.getNumberParser().parse(er).getValue()).floatValue()); + + int lastTwoYearNum = 0; + + MatchGroup lastTwoYear = match.getGroup("lasttwoyearnum"); + + if (!StringUtility.isNullOrEmpty(lastTwoYear.value)) { + er = new ExtractResult(); + er.setStart(lastTwoYear.index); + er.setLength(lastTwoYear.length); + er.setText(lastTwoYear.value); + + lastTwoYearNum = Math.round(Double.valueOf((double)config.getNumberParser().parse(er).getValue()).floatValue()); + } + + // Exclude pure number like "nineteen", "twenty four" + if (firstTwoYearNum < 100 && lastTwoYearNum == 0 || firstTwoYearNum < 100 && firstTwoYearNum % 10 == 0 && lastTwoYear.value.trim().split(" ").length == 1) { + year = Constants.InvalidYear; + return year; + } + + if (firstTwoYearNum >= 100) { + year = firstTwoYearNum + lastTwoYearNum; + } else { + year = firstTwoYearNum * 100 + lastTwoYearNum; + } + + } else { + + if (!StringUtility.isNullOrEmpty(writtenYearStr)) { + + MatchGroup writtenYear = match.getGroup("fullyear"); + + ExtractResult er = new ExtractResult(); + er.setStart(writtenYear.index); + er.setLength(writtenYear.length); + er.setText(writtenYear.value); + + year = Math.round(Double.valueOf((double)config.getNumberParser().parse(er).getValue()).floatValue()); + + if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { + year += 1900; + } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { + year += 2000; + } + } + } + } + + return year; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java new file mode 100644 index 000000000..76b57dab3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java @@ -0,0 +1,478 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public class BaseDateExtractor extends AbstractYearExtractor implements IDateTimeExtractor { + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATE; + } + + public BaseDateExtractor(IDateExtractorConfiguration config) { + super(config); + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + tokens.addAll(basicRegexMatch(input)); + tokens.addAll(implicitDate(input)); + tokens.addAll(numberWithMonth(input, reference)); + tokens.addAll(extractRelativeDurationDate(input, reference)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + // match basic patterns in DateRegexList + private Collection basicRegexMatch(String text) { + List result = new ArrayList<>(); + + for (Pattern regex : config.getDateRegexList()) { + Match[] matches = RegExpUtility.getMatches(regex, text); + + for (Match match : matches) { + // some match might be part of the date range entity, and might be splitted in a wrong way + + if (validateMatch(match, text)) { + // Cases that the relative term is before the detected date entity, like "this 5/12", "next friday 5/12" + String preText = text.substring(0, match.index); + ConditionalMatch relativeRegex = RegexExtension.matchEnd(config.getStrictRelativeRegex(), preText, true); + if (relativeRegex.getSuccess()) { + result.add(new Token(relativeRegex.getMatch().get().index, match.index + match.length)); + } else { + result.add(new Token(match.index, match.index + match.length)); + } + } + } + } + + return result; + } + + // this method is to validate whether the match is part of date range and is a correct split + // For example: in case "10-1 - 11-7", "10-1 - 11" can be matched by some of the Regexes, + // but the full text is a date range, so "10-1 - 11" is not a correct split + private boolean validateMatch(Match match, String text) { + // If the match doesn't contains "year" part, it will not be ambiguous and it's a valid match + boolean isValidMatch = StringUtility.isNullOrEmpty(match.getGroup("year").value); + + if (!isValidMatch) { + MatchGroup yearGroup = match.getGroup("year"); + + // If the "year" part is not at the end of the match, it's a valid match + if (yearGroup.index + yearGroup.length != match.index + match.length) { + isValidMatch = true; + } else { + String subText = text.substring(yearGroup.index); + + // If the following text (include the "year" part) doesn't start with a Date entity, it's a valid match + if (!startsWithBasicDate(subText)) { + isValidMatch = true; + } else { + // If the following text (include the "year" part) starts with a Date entity, + // but the following text (doesn't include the "year" part) also starts with a valid Date entity, + // the current match is still valid + // For example, "10-1-2018-10-2-2018". Match "10-1-2018" is valid because though "2018-10-2" a valid match + // (indicates the first year "2018" might belongs to the second Date entity), but "10-2-2018" is also a valid match. + subText = text.substring(yearGroup.index + yearGroup.length).trim(); + subText = trimStartRangeConnectorSymbols(subText); + isValidMatch = startsWithBasicDate(subText); + } + } + } + + return isValidMatch; + } + + // TODO: Simplify this method to improve the performance + private String trimStartRangeConnectorSymbols(String text) { + Match[] rangeConnectorSymbolMatches = RegExpUtility.getMatches(config.getRangeConnectorSymbolRegex(), text); + + for (Match symbolMatch : rangeConnectorSymbolMatches) { + int startSymbolLength = -1; + + if (symbolMatch.value != "" && symbolMatch.index == 0 && symbolMatch.length > startSymbolLength) { + startSymbolLength = symbolMatch.length; + } + + if (startSymbolLength > 0) { + text = text.substring(startSymbolLength); + } + } + + return text.trim(); + } + + // TODO: Simplify this method to improve the performance + private boolean startsWithBasicDate(String text) { + for (Pattern regex : config.getDateRegexList()) { + ConditionalMatch match = RegexExtension.matchBegin(regex, text, true); + + if (match.getSuccess()) { + return true; + } + } + + return false; + } + + // match several other cases + // including 'today', 'the day after tomorrow', 'on 13' + private Collection implicitDate(String text) { + List result = new ArrayList<>(); + + for (Pattern regex : config.getImplicitDateList()) { + Match[] matches = RegExpUtility.getMatches(regex, text); + + for (Match match : matches) { + result.add(new Token(match.index, match.index + match.length)); + } + } + + return result; + } + + // Check every integers and ordinal number for date + private Collection numberWithMonth(String text, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List ers = config.getOrdinalExtractor().extract(text); + ers.addAll(config.getIntegerExtractor().extract(text)); + + for (ExtractResult result : ers) { + int num; + try { + ParseResult parseResult = config.getNumberParser().parse(result); + num = Float.valueOf(parseResult.getValue().toString()).intValue(); + } catch (NumberFormatException e) { + num = 0; + } + + if (num < 1 || num > 31) { + continue; + } + + if (result.getStart() >= 0) { + // Handling cases like '(Monday,) Jan twenty two' + String frontStr = text.substring(0, result.getStart()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getMonthEnd(), frontStr)).findFirst(); + if (match.isPresent()) { + int startIndex = match.get().index; + int endIndex = match.get().index + match.get().length + result.getLength(); + + int month = config.getMonthOfYear().getOrDefault(match.get().getGroup("month").value.toLowerCase(), reference.getMonthValue()); + + Pair startEnd = extendWithWeekdayAndYear(startIndex, endIndex, month, num, text, reference); + + tokens.add(new Token(startEnd.getValue0(), startEnd.getValue1())); + continue; + } + + // Handling cases like 'for the 25th' + Match[] matches = RegExpUtility.getMatches(config.getForTheRegex(), text); + boolean isFound = false; + + for (Match matchCase : matches) { + if (matchCase != null) { + String ordinalNum = matchCase.getGroup("DayOfMonth").value; + if (ordinalNum.equals(result.getText())) { + int endLenght = 0; + if (!matchCase.getGroup("end").value.equals("")) { + endLenght = matchCase.getGroup("end").value.length(); + } + + tokens.add(new Token(matchCase.index, matchCase.index + matchCase.length - endLenght)); + isFound = true; + } + } + } + + if (isFound) { + continue; + } + + // Handling cases like 'Thursday the 21st', which both 'Thursday' and '21st' refer to a same date + matches = RegExpUtility.getMatches(config.getWeekDayAndDayOfMonthRegex(), text); + isFound = false; + for (Match matchCase : matches) { + if (matchCase != null) { + String ordinalNum = matchCase.getGroup("DayOfMonth").value; + if (ordinalNum.equals(result.getText())) { + // Get week of day for the ordinal number which is regarded as a date of reference month + LocalDateTime date = DateUtil.safeCreateFromMinValue(reference.getYear(), reference.getMonthValue(), num); + String numWeekDayStr = date.getDayOfWeek().toString().toLowerCase(); + + // Get week day from text directly, compare it with the weekday generated above + // to see whether they refer to the same week day + String extractedWeekDayStr = matchCase.getGroup("weekday").value.toLowerCase(); + int numWeekDay = config.getDayOfWeek().get(numWeekDayStr); + int extractedWeekDay = config.getDayOfWeek().get(extractedWeekDayStr); + + if (date != DateUtil.minValue() && numWeekDay == extractedWeekDay) { + tokens.add(new Token(matchCase.index, result.getStart() + result.getLength())); + isFound = true; + } + } + } + } + + if (isFound) { + continue; + } + + // Handling cases like '20th of next month' + String suffixStr = text.substring(result.getStart() + result.getLength()); + ConditionalMatch beginMatch = RegexExtension.matchBegin(config.getRelativeMonthRegex(), suffixStr.trim(), true); + if (beginMatch.getSuccess() && beginMatch.getMatch().get().index == 0) { + int spaceLen = suffixStr.length() - suffixStr.trim().length(); + int resStart = result.getStart(); + int resEnd = resStart + result.getLength() + spaceLen + beginMatch.getMatch().get().length; + + // Check if prefix contains 'the', include it if any + String prefix = text.substring(0, resStart); + Optional prefixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPrefixArticleRegex(), prefix)).findFirst(); + if (prefixMatch.isPresent()) { + resStart = prefixMatch.get().index; + } + + tokens.add(new Token(resStart, resEnd)); + } + + // Handling cases like 'second Sunday' + suffixStr = text.substring(result.getStart() + result.getLength()); + beginMatch = RegexExtension.matchBegin(config.getWeekDayRegex(), suffixStr.trim(), true); + if (beginMatch.getSuccess() && num >= 1 && num <= 5 && result.getType().equals("builtin.num.ordinal")) { + String weekDayStr = beginMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + if (config.getDayOfWeek().containsKey(weekDayStr)) { + int spaceLen = suffixStr.length() - suffixStr.trim().length(); + tokens.add(new Token(result.getStart(), result.getStart() + result.getLength() + spaceLen + beginMatch.getMatch().get().length)); + } + } + } + + // For cases like "I'll go back twenty second of June" + if (result.getStart() + result.getLength() < text.length()) { + String afterStr = text.substring(result.getStart() + result.getLength()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getOfMonth(), afterStr)).findFirst(); + if (match.isPresent()) { + int startIndex = result.getStart(); + int endIndex = result.getStart() + result.getLength() + match.get().length; + + int month = config.getMonthOfYear().getOrDefault(match.get().getGroup("month").value.toLowerCase(), reference.getMonthValue()); + + Pair startEnd = extendWithWeekdayAndYear(startIndex, endIndex, month, num, text, reference); + tokens.add(new Token(startEnd.getValue0(), startEnd.getValue1())); + } + } + } + + return tokens; + } + + private Pair extendWithWeekdayAndYear(int startIndex, int endIndex, int month, int day, String text, LocalDateTime reference) { + int year = reference.getYear(); + int startIndexResult = startIndex; + int endIndexResult = endIndex; + + // Check whether there's a year + String suffix = text.substring(endIndexResult); + Optional matchYear = Arrays.stream(RegExpUtility.getMatches(config.getYearSuffix(), suffix)).findFirst(); + + if (matchYear.isPresent() && matchYear.get().index == 0) { + year = getYearFromText(matchYear.get()); + + if (year >= Constants.MinYearNum && year <= Constants.MaxYearNum) { + endIndexResult += matchYear.get().length; + } + } + + LocalDateTime date = DateUtil.safeCreateFromMinValue(year, month, day); + + // Check whether there's a weekday + String prefix = text.substring(0, startIndexResult); + Optional matchWeekDay = Arrays.stream(RegExpUtility.getMatches(config.getWeekDayEnd(), prefix)).findFirst(); + if (matchWeekDay.isPresent()) { + // Get weekday from context directly, compare it with the weekday extraction above + // to see whether they are referred to the same weekday + String extractedWeekDayStr = matchWeekDay.get().getGroup("weekday").value.toLowerCase(); + String numWeekDayStr = date.getDayOfWeek().toString().toLowerCase(); + + if (config.getDayOfWeek().containsKey(numWeekDayStr) && config.getDayOfWeek().containsKey(extractedWeekDayStr)) { + int weekDay1 = config.getDayOfWeek().get(numWeekDayStr); + int weekday2 = config.getDayOfWeek().get(extractedWeekDayStr); + if (date != DateUtil.minValue() && weekDay1 == weekday2) { + startIndexResult = matchWeekDay.get().index; + } + + } + } + + return new Pair<>(startIndexResult, endIndexResult); + } + + // Cases like "3 days from today", "5 weeks before yesterday", "2 months after tomorrow" + // Note that these cases are of type "date" + private Collection extractRelativeDurationDate(String text, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List durations = config.getDurationExtractor().extract(text, reference); + + for (ExtractResult duration : durations) { + // if it is a multiple duration but its type is not equal to Date, skip it here + if (isMultipleDuration(duration) && !isMultipleDurationDate(duration)) { + continue; + } + + // Some types of duration can be compounded with "before", "after" or "from" suffix to create a "date" + // While some other types of durations, when compounded with such suffix, it will not create a "date", but create a "dateperiod" + // For example, durations like "3 days", "2 weeks", "1 week and 2 days", can be compounded with such suffix to create a "date" + // But "more than 3 days", "less than 2 weeks", when compounded with such suffix, it will become cases + // like "more than 3 days from today" which is a "dateperiod", not a "date" + // As this parent method is aimed to extract RelativeDurationDate, so for cases with "more than" or "less than", + // we remove the prefix so as to extract the expected RelativeDurationDate + if (isInequalityDuration(duration)) { + duration = stripInequalityDuration(duration); + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), duration.getText())).findFirst(); + + if (match.isPresent()) { + tokens = AgoLaterUtil.extractorDurationWithBeforeAndAfter(text, duration, tokens, config.getUtilityConfiguration()); + } + + } + + // Extract cases like "in 3 weeks", which equals to "3 weeks from today" + List relativeDurationDateWithInPrefix = extractRelativeDurationDateWithInPrefix(text, durations, reference); + + // For cases like "in 3 weeks from today", we should choose "3 weeks from today" as the extract result rather than "in 3 weeks" or "in 3 weeks from today" + for (Token erWithInPrefix : relativeDurationDateWithInPrefix) { + if (!isOverlapWithExistExtractions(erWithInPrefix, tokens)) { + tokens.add(erWithInPrefix); + } + } + + return tokens; + } + + public boolean isOverlapWithExistExtractions(Token er, List existErs) { + for (Token existEr : existErs) { + if (er.getStart() < existEr.getEnd() && er.getEnd() > existEr.getStart()) { + return true; + } + } + + return false; + } + + // "In 3 days/weeks/months/years" = "3 days/weeks/months/years from now" + public List extractRelativeDurationDateWithInPrefix(String text, List durationEr, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List durations = new ArrayList<>(); + + for (ExtractResult durationExtraction : durationEr) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), durationExtraction.getText())).findFirst(); + if (match.isPresent()) { + int start = durationExtraction.getStart() != null ? durationExtraction.getStart() : 0; + int end = start + (durationExtraction.getLength() != null ? durationExtraction.getLength() : 0); + durations.add(new Token(start, end)); + } + } + + for (Token duration : durations) { + String beforeStr = text.substring(0, duration.getStart()).toLowerCase(); + String afterStr = text.substring(duration.getStart() + duration.getLength()).toLowerCase(); + + if (StringUtility.isNullOrWhiteSpace(beforeStr) && StringUtility.isNullOrWhiteSpace(afterStr)) { + continue; + } + + ConditionalMatch match = RegexExtension.matchEnd(config.getInConnectorRegex(), beforeStr, true); + + if (match.getSuccess() && match.getMatch().isPresent()) { + int startToken = match.getMatch().get().index; + Optional rangeUnitMatch = Arrays.stream( + RegExpUtility.getMatches(config.getRangeUnitRegex(), + text.substring(duration.getStart(), + duration.getStart() + duration.getLength()))).findFirst(); + + if (rangeUnitMatch.isPresent()) { + tokens.add(new Token(startToken, duration.getEnd())); + } + } + } + + return tokens; + } + + private ExtractResult stripInequalityDuration(ExtractResult er) { + ExtractResult result = er; + result = stripInequalityPrefix(result, config.getMoreThanRegex()); + result = stripInequalityPrefix(result, config.getLessThanRegex()); + return result; + } + + private ExtractResult stripInequalityPrefix(ExtractResult er, Pattern regex) { + ExtractResult result = er; + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, er.getText())).findFirst(); + + if (match.isPresent()) { + int originalLength = er.getText().length(); + String text = er.getText().replace(match.get().value, "").trim(); + int start = er.getStart() + originalLength - text.length(); + int length = text.length(); + String data = ""; + result.setStart(start); + result.setLength(length); + result.setText(text); + result.setData(data); + } + + return result; + } + + // Cases like "more than 3 days", "less than 4 weeks" + private boolean isInequalityDuration(ExtractResult er) { + return er.getData() != null && (er.getData().toString().equals(Constants.MORE_THAN_MOD) || er.getData().toString().equals(Constants.LESS_THAN_MOD)); + } + + private boolean isMultipleDurationDate(ExtractResult er) { + return er.getData() != null && er.getData().toString().equals(Constants.MultipleDuration_Date); + } + + private boolean isMultipleDuration(ExtractResult er) { + return er.getData() != null && er.getData().toString().startsWith(Constants.MultipleDuration_Prefix); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java new file mode 100644 index 000000000..19d792327 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java @@ -0,0 +1,464 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class BaseDatePeriodExtractor implements IDateTimeExtractor { + + private final IDatePeriodExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATEPERIOD; + } + + public BaseDatePeriodExtractor(IDatePeriodExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + tokens.addAll(matchSimpleCases(input)); + + List simpleCasesResults = Token.mergeAllTokens(tokens, input, getExtractorName()); + List ordinalExtractions = config.getOrdinalExtractor().extract(input); + + tokens.addAll(mergeTwoTimePoints(input, reference)); + tokens.addAll(matchDuration(input, reference)); + tokens.addAll(singleTimePointWithPatterns(input, ordinalExtractions, reference)); + tokens.addAll(matchComplexCases(input, simpleCasesResults, reference)); + tokens.addAll(matchYearPeriod(input, reference)); + tokens.addAll(matchOrdinalNumberWithCenturySuffix(input, ordinalExtractions)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + private List matchSimpleCases(String input) { + List results = new ArrayList<>(); + + for (Pattern regex : config.getSimpleCasesRegexes()) { + Match[] matches = RegExpUtility.getMatches(regex, input); + + for (Match match : matches) { + Optional matchYear = Arrays.stream(RegExpUtility.getMatches(config.getYearRegex(), match.value)).findFirst(); + + if (matchYear.isPresent() && matchYear.get().length == match.length) { + int year = ((BaseDateExtractor)config.getDatePointExtractor()).getYearFromText(matchYear.get()); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + continue; + } + } + + // handle single year which is surrounded by '-' at both sides, e.g., a single year falls in a GUID + if (match.length == Constants.FourDigitsYearLength && + RegExpUtility.getMatches(this.config.getYearRegex(), match.value).length > 0 && + infixBoundaryCheck(match, input)) { + String subStr = input.substring(match.index - 1, match.index - 1 + 6); + if (RegExpUtility.getMatches(this.config.getIllegalYearRegex(), subStr).length > 0) { + continue; + } + } + + results.add(new Token(match.index, match.index + match.length)); + } + + } + + return results; + } + + private List mergeTwoTimePoints(String input, LocalDateTime reference) { + List ers = config.getDatePointExtractor().extract(input, reference); + + // Handle "now" + Match[] matches = RegExpUtility.getMatches(this.config.getNowRegex(), input); + if (matches.length != 0) { + for (Match match : matches) { + ers.add(new ExtractResult(match.index, match.length, match.value, Constants.SYS_DATETIME_DATE)); + } + + ers.sort(Comparator.comparingInt(arg -> arg.getStart())); + } + + return mergeMultipleExtractions(input, ers); + } + + private List mergeMultipleExtractions(String input, List extractionResults) { + List results = new ArrayList<>(); + + Metadata metadata = new Metadata() { + { + setPossiblyIncludePeriodEnd(true); + } + }; + + if (extractionResults.size() <= 1) { + return results; + } + + int idx = 0; + + while (idx < extractionResults.size() - 1) { + ExtractResult thisResult = extractionResults.get(idx); + ExtractResult nextResult = extractionResults.get(idx + 1); + + int middleBegin = thisResult.getStart() + thisResult.getLength(); + int middleEnd = nextResult.getStart(); + if (middleBegin >= middleEnd) { + idx++; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(); + + if (RegexExtension.isExactMatch(config.getTillRegex(), middleStr, true)) { + int periodBegin = thisResult.getStart(); + int periodEnd = nextResult.getStart() + nextResult.getLength(); + + // handle "from/between" together with till words (till/until/through...) + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex fromIndex = config.getFromTokenIndex(beforeStr); + ResultIndex betweenIndex = config.getBetweenTokenIndex(beforeStr); + + if (fromIndex.getResult()) { + periodBegin = fromIndex.getIndex(); + } else if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + } + + results.add(new Token(periodBegin, periodEnd, metadata)); + + // merge two tokens here, increase the index by two + idx += 2; + continue; + } + + boolean hasConnectorToken = config.hasConnectorToken(middleStr); + if (hasConnectorToken) { + int periodBegin = thisResult.getStart(); + int periodEnd = nextResult.getStart() + nextResult.getLength(); + + // handle "between...and..." case + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex beforeIndex = config.getBetweenTokenIndex(beforeStr); + + if (beforeIndex.getResult()) { + periodBegin = beforeIndex.getIndex(); + results.add(new Token(periodBegin, periodEnd, metadata)); + + // merge two tokens here, increase the index by two + idx += 2; + continue; + } + } + idx++; + } + + return results; + } + + private List matchDuration(String input, LocalDateTime reference) { + + List results = new ArrayList<>(); + + List durations = new ArrayList<>(); + Iterable durationExtractions = config.getDurationExtractor().extract(input, reference); + + for (ExtractResult durationExtraction : durationExtractions) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), durationExtraction.getText())).findFirst(); + if (match.isPresent()) { + durations.add(new Token(durationExtraction.getStart(), durationExtraction.getStart() + durationExtraction.getLength())); + } + } + + for (Token duration : durations) { + String beforeStr = input.substring(0, duration.getStart()).toLowerCase(); + String afterStr = input.substring(duration.getStart() + duration.getLength()).toLowerCase(); + + if (StringUtility.isNullOrWhiteSpace(beforeStr) && StringUtility.isNullOrWhiteSpace(afterStr)) { + continue; + } + + // within "Days/Weeks/Months/Years" should be handled as dateRange here + // if duration contains "Seconds/Minutes/Hours", it should be treated as datetimeRange + ConditionalMatch match = RegexExtension.matchEnd(config.getWithinNextPrefixRegex(), beforeStr, true); + + if (match.getSuccess()) { + int startToken = match.getMatch().get().index; + String tokenString = input.substring(duration.getStart(), duration.getEnd()); + Match matchDate = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), tokenString)).findFirst().orElse(null); + Match matchTime = Arrays.stream(RegExpUtility.getMatches(config.getTimeUnitRegex(), tokenString)).findFirst().orElse(null); + + if (matchDate != null && matchTime == null) { + results.add(new Token(startToken, duration.getEnd())); + continue; + } + } + + // Match prefix + match = RegexExtension.matchEnd(config.getPastRegex(), beforeStr, true); + + int index = -1; + + if (match.getSuccess()) { + index = match.getMatch().get().index; + } + + if (index < 0) { + // For cases like "next five days" + match = RegexExtension.matchEnd(config.getFutureRegex(), beforeStr, true); + + if (match.getSuccess()) { + index = match.getMatch().get().index; + } + } + + if (index >= 0) { + String prefix = beforeStr.substring(0, index).trim(); + String durationText = input.substring(duration.getStart(), duration.getStart() + duration.getLength()); + List numbersInPrefix = config.getCardinalExtractor().extract(prefix); + List numbersInDuration = config.getCardinalExtractor().extract(durationText); + + // Cases like "2 upcoming days", should be supported here + // Cases like "2 upcoming 3 days" is invalid, only extract "upcoming 3 days" by default + if (!numbersInPrefix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult lastNumber = numbersInPrefix.stream() + .sorted(Comparator.comparingInt(x -> x.getStart() + x.getLength())) + .reduce((acc, item) -> item).orElse(null); + + // Prefix should ends with the last number + if (lastNumber.getStart() + lastNumber.getLength() == prefix.length()) { + results.add(new Token(lastNumber.getStart(), duration.getEnd())); + } + + } else { + results.add(new Token(index, duration.getEnd())); + } + + continue; + } + + // Match suffix + match = RegexExtension.matchBegin(config.getPastRegex(), afterStr, true); + if (match.getSuccess()) { + int matchLength = match.getMatch().get().index + match.getMatch().get().length; + results.add(new Token(duration.getStart(), duration.getEnd() + matchLength)); + continue; + } + + match = RegexExtension.matchBegin(config.getFutureSuffixRegex(), afterStr, true); + if (match.getSuccess()) { + int matchLength = match.getMatch().get().index + match.getMatch().get().length; + results.add(new Token(duration.getStart(), duration.getEnd() + matchLength)); + } + } + + return results; + } + + // 1. Extract the month of date, week of date to a date range + // 2. Extract cases like within two weeks from/before today/tomorrow/yesterday + private List singleTimePointWithPatterns(String input, List ordinalExtractions, LocalDateTime reference) { + List results = new ArrayList<>(); + + List datePoints = config.getDatePointExtractor().extract(input, reference); + + // For cases like "week of the 18th" + datePoints.addAll(ordinalExtractions.stream().filter(o -> datePoints.stream().noneMatch(er -> er.isOverlap(o))).collect(Collectors.toList())); + + if (datePoints.size() < 1) { + return results; + } + + for (ExtractResult er : datePoints) { + if (er.getStart() != null && er.getLength() != null) { + String beforeStr = input.substring(0, er.getStart()); + results.addAll(getTokenForRegexMatching(beforeStr, config.getWeekOfRegex(), er)); + results.addAll(getTokenForRegexMatching(beforeStr, config.getMonthOfRegex(), er)); + + // Cases like "3 days from today", "2 weeks before yesterday", "3 months after tomorrow" + if (isRelativeDurationDate(er)) { + results.addAll(getTokenForRegexMatching(beforeStr, config.getLessThanRegex(), er)); + results.addAll(getTokenForRegexMatching(beforeStr, config.getMoreThanRegex(), er)); + + // For "within" case, only duration with relative to "today" or "now" makes sense + // Cases like "within 3 days from yesterday/tomorrow" does not make any sense + if (isDateRelativeToNowOrToday(er)) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getWithinNextPrefixRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + boolean isNext = !StringUtility.isNullOrEmpty(match.get().getGroup(Constants.NextGroupName).value); + + // For "within" case + // Cases like "within the next 5 days before today" is not acceptable + if (!(isNext && isAgoRelativeDurationDate(er))) { + results.addAll(getTokenForRegexMatching(beforeStr, config.getWithinNextPrefixRegex(), er)); + } + } + } + } + } + } + + return results; + } + + private boolean isAgoRelativeDurationDate(ExtractResult er) { + return Arrays.stream(RegExpUtility.getMatches(config.getAgoRegex(), er.getText())).findAny().isPresent(); + } + + // Cases like "3 days from today", "2 weeks before yesterday", "3 months after + // tomorrow" + private boolean isRelativeDurationDate(ExtractResult er) { + boolean isAgo = Arrays.stream(RegExpUtility.getMatches(config.getAgoRegex(), er.getText())).findAny().isPresent(); + boolean isLater = Arrays.stream(RegExpUtility.getMatches(config.getLaterRegex(), er.getText())).findAny().isPresent(); + + return isAgo || isLater; + } + + private List getTokenForRegexMatching(String source, Pattern regex, ExtractResult er) { + List results = new ArrayList<>(); + Match match = Arrays.stream(RegExpUtility.getMatches(regex, source)).findFirst().orElse(null); + if (match != null && source.trim().endsWith(match.value.trim())) { + int startIndex = source.lastIndexOf(match.value); + results.add(new Token(startIndex, er.getStart() + er.getLength())); + } + + return results; + } + + // Complex cases refer to the combination of daterange and datepoint + // For Example: from|between {DateRange|DatePoint} to|till|and {DateRange|DatePoint} + private List matchComplexCases(String input, List simpleCasesResults, LocalDateTime reference) { + List ers = config.getDatePointExtractor().extract(input, reference); + + // Filter out DateRange results that are part of DatePoint results + // For example, "Feb 1st 2018" => "Feb" and "2018" should be filtered out here + List simpleErs = simpleCasesResults.stream().filter(simpleDateRange -> filterErs(simpleDateRange, ers)).collect(Collectors.toList()); + ers.addAll(simpleErs); + + List results = ers.stream().sorted((o1, o2) -> o1.getStart().compareTo(o2.getStart())).collect(Collectors.toList()); + + return mergeMultipleExtractions(input, results); + } + + private boolean filterErs(ExtractResult simpleDateRange, List ers) { + return !ers.stream().anyMatch(datePoint -> compareErs(simpleDateRange, datePoint)); + } + + private boolean compareErs(ExtractResult simpleDateRange, ExtractResult datePoint) { + return datePoint.getStart() <= simpleDateRange.getStart() && datePoint.getStart() + datePoint.getLength() >= simpleDateRange.getStart() + simpleDateRange.getLength(); + } + + private List matchYearPeriod(String input, LocalDateTime reference) { + List results = new ArrayList<>(); + Metadata metadata = new Metadata() { + { + setPossiblyIncludePeriodEnd(true); + } + }; + + Match[] matches = RegExpUtility.getMatches(config.getYearPeriodRegex(), input); + for (Match match : matches) { + Match matchYear = Arrays.stream(RegExpUtility.getMatches(config.getYearRegex(), match.value)).findFirst().orElse(null); + if (matchYear != null && matchYear.length == match.value.length()) { + int year = ((BaseDateExtractor)config.getDatePointExtractor()).getYearFromText(matchYear); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + continue; + } + // Possibly include period end only apply for cases like "2014-2018", which are not single year cases + metadata.setPossiblyIncludePeriodEnd(false); + } else { + Match[] yearMatches = RegExpUtility.getMatches(config.getYearRegex(), match.value); + boolean isValidYear = true; + for (Match yearMatch : yearMatches) { + int year = ((BaseDateExtractor)config.getDatePointExtractor()).getYearFromText(yearMatch); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + isValidYear = false; + break; + } + } + + if (!isValidYear) { + continue; + } + + } + + results.add(new Token(match.index, match.index + match.length, metadata)); + } + + return results; + } + + private List matchOrdinalNumberWithCenturySuffix(String input, List ordinalExtractions) { + List results = new ArrayList<>(); + + for (ExtractResult er : ordinalExtractions) { + if (er.getStart() + er.getLength() >= input.length()) { + continue; + } + + String afterStr = input.substring(er.getStart() + er.getLength()); + String trimmedAfterStr = afterStr.trim(); + int whiteSpacesCount = afterStr.length() - trimmedAfterStr.length(); + int afterStringOffset = er.getStart() + er.getLength() + whiteSpacesCount; + + Match match = Arrays.stream(RegExpUtility.getMatches(config.getCenturySuffixRegex(), trimmedAfterStr)).findFirst().orElse(null); + + if (match != null) { + results.add(new Token(er.getStart(), afterStringOffset + match.index + match.length)); + } + } + + return results; + } + + private boolean isDateRelativeToNowOrToday(ExtractResult input) { + for (String flagWord : config.getDurationDateRestrictions()) { + if (input.getText().contains(flagWord)) { + return true; + } + } + + return false; + } + + // check whether the match is an infix of source + private boolean infixBoundaryCheck(Match match, String source) { + boolean isMatchInfixOfSource = false; + if (match.index > 0 && match.index + match.length < source.length()) { + if (source.substring(match.index, match.index + match.length).equals(match.value)) { + isMatchInfixOfSource = true; + } + } + + return isMatchInfixOfSource; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java new file mode 100644 index 000000000..13362108b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java @@ -0,0 +1,598 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtendedModelResult; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class BaseDateTimeAltExtractor implements IDateTimeListExtractor { + + private final IDateTimeAltExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATETIMEALT; + } + + public BaseDateTimeAltExtractor(IDateTimeAltExtractorConfiguration config) { + this.config = config; + } + + public List extract(List extractResults, String text) { + return this.extract(extractResults, text, LocalDateTime.now()); + } + + @Override + public List extract(List extractResults, String text, LocalDateTime reference) { + return extractAlt(extractResults, text, reference); + } + + // Modify time entity to an alternative DateTime expression, such as "8pm" in "Monday 7pm or 8pm" + // or "Thursday" in "next week on Tuesday or Thursday" + private List extractAlt(List extractResults, String text, LocalDateTime reference) { + List ers = addImplicitDates(extractResults, text); + + // Sort the extracted results for the further sequential process. + ers.sort(Comparator.comparingInt(erA -> erA.getStart())); + + int i = 0; + while (i < ers.size() - 1) { + List altErs = getAltErsWithSameParentText(ers, i, text); + + if (altErs.size() == 0) { + i++; + continue; + } + + int j = i + altErs.size() - 1; + + int parentTextStart = ers.get(i).getStart(); + int parentTextLen = ers.get(j).getStart() + ers.get(j).getLength() - ers.get(i).getStart(); + String parentText = text.substring(parentTextStart, parentTextStart + parentTextLen); + + boolean success = extractAndApplyMetadata(altErs, parentText); + + if (success) { + i = j + 1; + } else { + i++; + } + } + + ers = resolveImplicitRelativeDatePeriod(ers, text); + ers = pruneInvalidImplicitDate(ers); + + return ers; + } + + private List getAltErsWithSameParentText(List ers, int startIndex, String text) { + int pivot = startIndex + 1; + HashSet types = new HashSet(); + types.add(ers.get(startIndex).getType()); + + while (pivot < ers.size()) { + // Currently only support merge two kinds of types + if (!types.contains(ers.get(pivot).getType()) && types.size() > 1) { + break; + } + + // Check whether middle string is a connector + int middleBegin = ers.get(pivot - 1).getStart() + ers.get(pivot - 1).getLength(); + int middleEnd = ers.get(pivot).getStart(); + + if (!isConnectorOrWhiteSpace(middleBegin, middleEnd, text)) { + break; + } + + int prefixEnd = ers.get(pivot - 1).getStart(); + String prefixStr = text.substring(0, prefixEnd); + + if (isEndsWithRangePrefix(prefixStr)) { + break; + } + + if (isSupportedAltEntitySequence(ers.subList(startIndex, startIndex + (pivot - startIndex + 1)))) { + types.add(ers.get(pivot).getType()); + pivot++; + } else { + break; + } + } + + pivot--; + + if (startIndex == pivot) { + startIndex++; + } + + return ers.subList(startIndex, startIndex + (pivot - startIndex + 1)); + } + + private List addImplicitDates(List originalErs, String text) { + List result = new ArrayList<>(); + + Match[] implicitDateMatches = RegExpUtility.getMatches(config.getDayRegex(), text); + int i = 0; + originalErs.sort(Comparator.comparingInt(er -> er.getStart())); + + for (Match dateMatch : implicitDateMatches) { + boolean notBeContained = true; + while (i < originalErs.size()) { + if (originalErs.get(i).getStart() <= dateMatch.index && originalErs.get(i).getStart() + originalErs.get(i).getLength() >= dateMatch.index + dateMatch.length) { + notBeContained = false; + break; + } + + if (originalErs.get(i).getStart() + originalErs.get(i).getLength() < dateMatch.index + dateMatch.length) { + i++; + } else if (originalErs.get(i).getStart() + originalErs.get(i).getLength() >= dateMatch.index + dateMatch.length) { + break; + } + } + + ExtractResult dateEr = new ExtractResult( + dateMatch.index, + dateMatch.length, + dateMatch.value, + Constants.SYS_DATETIME_DATE); + + dateEr.setData(getExtractorName()); + if (notBeContained) { + result.add(dateEr); + } else if (i + 1 < originalErs.size()) { + // For cases like "I am looking at 18 and 19 June" + // in which "18" is wrongly recognized as time without context. + ExtractResult nextEr = originalErs.get(i + 1); + if (nextEr.getType().equals(Constants.SYS_DATETIME_DATE) && + originalErs.get(i).getText().equals(dateEr.getText()) && + isConnectorOrWhiteSpace(dateEr.getStart() + dateEr.getLength(), nextEr.getStart(), text)) { + result.add(dateEr); + originalErs.remove(i); + } + } + } + + result.addAll(originalErs); + result.sort(Comparator.comparingInt(er -> er.getStart())); + + return result; + } + + private List pruneInvalidImplicitDate(List ers) { + ers.removeIf(er -> { + if (er.getData() != null && er.getType().equals(Constants.SYS_DATETIME_DATE) && er.getData().equals(getExtractorName())) { + return true; + } + return false; + }); + + return ers; + } + + // Resolve cases like "this week or next". + private List resolveImplicitRelativeDatePeriod(List ers, String text) { + List relativeTermsMatches = new ArrayList<>(); + for (Pattern regex : config.getRelativePrefixList()) { + relativeTermsMatches.addAll(Arrays.asList(RegExpUtility.getMatches(regex, text))); + } + + List results = new ArrayList<>(); + + List relativeDatePeriodErs = new ArrayList<>(); + int i = 0; + for (ExtractResult result : ers.toArray(new ExtractResult[0])) { + if (!result.getType().equals(Constants.SYS_DATETIME_DATETIMEALT)) { + int resultEnd = result.getStart() + result.getLength(); + for (Match relativeTermsMatch : relativeTermsMatches) { + int relativeTermsMatchEnd = relativeTermsMatch.index + relativeTermsMatch.length; + if (relativeTermsMatch.index > resultEnd || relativeTermsMatchEnd < result.getStart()) { + // Check whether middle string is a connector + int middleBegin = relativeTermsMatch.index > resultEnd ? resultEnd : relativeTermsMatchEnd; + int middleEnd = relativeTermsMatch.index > resultEnd ? relativeTermsMatch.index : result.getStart(); + String middleStr = text.substring(middleBegin, middleEnd).trim().toLowerCase(); + Match[] orTermMatches = RegExpUtility.getMatches(config.getOrRegex(), middleStr); + if (orTermMatches.length == 1 && orTermMatches[0].index == 0 && orTermMatches[0].length == middleStr.length()) { + int parentTextStart = relativeTermsMatch.index > resultEnd ? result.getStart() : relativeTermsMatch.index; + int parentTextEnd = relativeTermsMatch.index > resultEnd ? relativeTermsMatchEnd : resultEnd; + String parentText = text.substring(parentTextStart, parentTextEnd); + + ExtractResult contextErs = new ExtractResult(); + for (Pattern regex : config.getRelativePrefixList()) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, result.getText())).findFirst(); + if (match.isPresent()) { + int matchEnd = match.get().index + match.get().length; + contextErs = new ExtractResult( + matchEnd, + result.getLength() - matchEnd, + result.getText().substring(matchEnd, result.getLength()), + Constants.ContextType_RelativeSuffix); + break; + } + } + + Map customData = new LinkedHashMap<>(); + customData.put(Constants.SubType, result.getType()); + customData.put(ExtendedModelResult.ParentTextKey, parentText); + customData.put(Constants.Context, contextErs); + + relativeDatePeriodErs.add(new ExtractResult( + relativeTermsMatch.index, + relativeTermsMatch.length, + relativeTermsMatch.value, + Constants.SYS_DATETIME_DATETIMEALT, + customData)); + + Map resultData = new LinkedHashMap<>(); + resultData.put(Constants.SubType, result.getType()); + resultData.put(ExtendedModelResult.ParentTextKey, parentText); + + result.setData(resultData); + result.setType(Constants.SYS_DATETIME_DATETIMEALT); + ers.set(i, result); + } + } + } + } + i++; + } + + results.addAll(ers); + results.addAll(relativeDatePeriodErs); + results.sort(Comparator.comparingInt(er -> er.getStart())); + + return results; + } + + private boolean isConnectorOrWhiteSpace(int start, int end, String text) { + if (end <= start) { + return false; + } + + String middleStr = text.substring(start, end).trim().toLowerCase(); + + if (StringUtility.isNullOrEmpty(middleStr)) { + return true; + } + + Match[] orTermMatches = RegExpUtility.getMatches(config.getOrRegex(), middleStr); + + return orTermMatches.length == 1 && orTermMatches[0].index == 0 && orTermMatches[0].length == middleStr.length(); + } + + private boolean isEndsWithRangePrefix(String prefixText) { + return RegexExtension.matchEnd(config.getRangePrefixRegex(), prefixText, true).getSuccess(); + } + + private boolean extractAndApplyMetadata(List extractResults, String parentText) { + boolean success = extractAndApplyMetadata(extractResults, parentText, false); + + if (!success) { + success = extractAndApplyMetadata(extractResults, parentText, true); + } + + if (!success && shouldApplyParentText(extractResults)) { + success = applyParentText(extractResults, parentText); + } + + return success; + } + + private boolean extractAndApplyMetadata(List extractResults, String parentText, boolean reverse) { + if (reverse) { + Collections.reverse(extractResults); + } + + boolean success = false; + + // Currently, we support alt entity sequence only when the second alt entity to the last alt entity share the same type + if (isSupportedAltEntitySequence(extractResults)) { + HashMap metadata = extractMetadata(extractResults.stream().findFirst().get(), parentText, extractResults); + HashMap metadataCandidate = null; + + int i = 0; + while (i < extractResults.size()) { + if (metadata == null) { + break; + } + + int j = i + 1; + + while (j < extractResults.size()) { + metadataCandidate = extractMetadata(extractResults.get(j), parentText, extractResults); + + // No context extracted, the context would follow the previous one + // Such as "Wednesday" in "next Tuesday or Wednesday" + if (metadataCandidate == null) { + j++; + } else { + // Current extraction has context, the context would not follow the previous ones + // Such as "Wednesday" in "next Monday or Tuesday or previous Wednesday" + break; + } + } + List ersShareContext = extractResults.subList(i, j); + applyMetadata(ersShareContext, metadata, parentText); + metadata = metadataCandidate; + + i = j; + success = true; + } + } + + return success; + } + + private boolean shouldApplyParentText(List extractResults) { + boolean shouldApply = false; + + if (isSupportedAltEntitySequence(extractResults)) { + String firstEntityType = extractResults.stream().findFirst().get().getType(); + String lastEntityType = extractResults.get(extractResults.size() - 1).getType(); + + if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + shouldApply = true; // "11/20 or 11/22" + } else if (firstEntityType.equals(Constants.SYS_DATETIME_TIME) && lastEntityType.equals(Constants.SYS_DATETIME_TIME)) { + shouldApply = true; // "7 oclock or 8 oclock" + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIME) && lastEntityType.equals(Constants.SYS_DATETIME_DATETIME)) { + shouldApply = true; // "Monday 1pm or Tuesday 2pm" + } + } + + return shouldApply; + } + + private boolean applyParentText(List extractResults, String parentText) { + boolean success = false; + + if (isSupportedAltEntitySequence(extractResults)) { + for (int i = 0; i < extractResults.size(); i++) { + ExtractResult extractResult = extractResults.get(i); + Map metadata = createMetadata(extractResult.getType(), parentText, null); + Map data = mergeMetadata(extractResult.getData(), metadata); + extractResult.setData(data); + extractResult.setType(getExtractorName()); + extractResults.set(i, extractResult); + } + + success = true; + } + + return success; + } + + private boolean isSupportedAltEntitySequence(List altEntities) { + Stream subSeq = altEntities.stream().skip(1); + List entityTypes = new ArrayList<>(); + + for (ExtractResult er : subSeq.toArray(ExtractResult[]::new)) { + if (!entityTypes.contains(er.getType())) { + entityTypes.add(er.getType()); + } + } + + return entityTypes.size() == 1; + } + + // This method is to extract metadata from the targeted ExtractResult + // For cases like "next week Monday or Tuesday or previous Wednesday", ExtractMethods can be more than one + private HashMap extractMetadata(ExtractResult targetEr, String parentText, List ers) { + HashMap metadata = null; + ArrayList>> extractMethods = getExtractMethods(targetEr.getType(), ers.get(ers.size() - 1).getType()); + BiConsumer postProcessMethod = getPostProcessMethod(targetEr.getType(), ers.get(ers.size() - 1).getType()); + ExtractResult contextEr = extractContext(targetEr, extractMethods, postProcessMethod); + + if (shouldCreateMetadata(ers, contextEr)) { + metadata = createMetadata(targetEr.getType(), parentText, contextEr); + } + + return metadata; + } + + private ExtractResult extractContext(ExtractResult er, ArrayList>> extractMethods, BiConsumer postProcessMethod) { + ExtractResult contextEr = null; + + for (Function> extractMethod : extractMethods) { + List contextErCandidates = extractMethod.apply(er.getText()); + if (contextErCandidates.size() == 1) { + contextEr = contextErCandidates.get(contextErCandidates.size() - 1); + break; + } + } + + if (contextEr != null && postProcessMethod != null) { + postProcessMethod.accept(contextEr, er); + } + + if (contextEr != null && StringUtility.isNullOrEmpty(contextEr.getText())) { + contextEr = null; + } + + return contextEr; + } + + private boolean shouldCreateMetadata(List originalErs, ExtractResult contextEr) { + // For alternative entities sequence which are all DatePeriod, we should create metadata even if context is null + return (contextEr != null || + (originalErs.get(0).getType() == Constants.SYS_DATETIME_DATEPERIOD && originalErs.get(originalErs.size() - 1).getType() == Constants.SYS_DATETIME_DATEPERIOD)); + } + + private void applyMetadata(List ers, HashMap metadata, String parentText) { + // The first extract results don't need any context + ExtractResult first = ers.stream().findFirst().orElse(null); + + // Share the timeZone info + Map metaDataOrigin = (HashMap)first.getData(); + if (metaDataOrigin != null && metaDataOrigin.containsKey(Constants.SYS_DATETIME_TIMEZONE)) { + metadata.put(Constants.SYS_DATETIME_TIMEZONE, metaDataOrigin.get(Constants.SYS_DATETIME_TIMEZONE)); + } + + HashMap metadataWithoutContext = createMetadata(first.getType(), parentText, null); + first.setData(mergeMetadata(first.getData(), metadataWithoutContext)); + first.setType(getExtractorName()); + ers.set(0, first); + + for (int i = 1; i < ers.size(); i++) { + ExtractResult er = ers.get(i); + er.setData(mergeMetadata(ers.get(i).getData(), metadata)); + er.setType(getExtractorName()); + ers.set(i, er); + } + } + + private Map mergeMetadata(Object originalMetadata, Map newMetadata) { + Map result = new HashMap<>(); + + if (originalMetadata instanceof Map) { + result = (Map)originalMetadata; + } + + if (originalMetadata == null) { + result = newMetadata; + } else { + for (Map.Entry data : newMetadata.entrySet()) { + result.put(data.getKey(), data.getValue()); + } + } + + return result; + } + + private HashMap createMetadata(String subType, String parentText, ExtractResult contextEr) { + HashMap data = new HashMap<>(); + + if (!StringUtility.isNullOrEmpty(subType)) { + data.put(Constants.SubType, subType); + } + + if (!StringUtility.isNullOrEmpty(parentText)) { + data.put(ExtendedModelResult.ParentTextKey, parentText); + } + + if (contextEr != null) { + data.put(Constants.Context, contextEr); + } + + return data; + } + + private List extractRelativePrefixContext(String entityText) { + List results = new ArrayList<>(); + + for (Pattern pattern : config.getRelativePrefixList()) { + Matcher match = pattern.matcher(entityText); + + if (match.find()) { + ExtractResult er = new ExtractResult(); + er.setText(match.group()); + er.setStart(match.start()); + er.setLength(match.end() - match.start()); + er.setType(Constants.ContextType_RelativePrefix); + results.add(er); + } + } + + return results; + } + + private List extractAmPmContext(String entityText) { + List results = new ArrayList<>(); + for (Pattern pattern : config.getAmPmRegexList()) { + Matcher match = pattern.matcher(entityText); + if (match.find()) { + ExtractResult er = new ExtractResult(); + er.setText(match.group()); + er.setStart(match.start()); + er.setLength(match.end() - match.start()); + er.setType(Constants.ContextType_AmPm); + results.add(er); + } + } + + return results; + } + + private BiConsumer getPostProcessMethod(String firstEntityType, String lastEntityType) { + if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + return (contextEr, originalEr) -> { + contextEr.setText(originalEr.getText().substring(0, contextEr.getStart()) + originalEr.getText().substring(contextEr.getStart() + contextEr.getLength())); + contextEr.setType(Constants.ContextType_RelativeSuffix); + }; + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + return (contextEr, originalEr) -> { + contextEr.setText(originalEr.getText().substring(0, contextEr.getStart())); + }; + } + + return null; + } + + private ArrayList>> getExtractMethods(String firstEntityType, String lastEntityType) { + ArrayList>> methods = new ArrayList<>(); + + if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIME) && lastEntityType.equals(Constants.SYS_DATETIME_TIME)) { + + // "Monday 7pm or 8pm" + methods.add(config.getDateExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + + // "next week Monday or Tuesday", "previous Monday or Wednesday" + methods.add(config.getDatePeriodExtractor()::extract); + methods.add(this::extractRelativePrefixContext); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_TIME) && lastEntityType.equals(Constants.SYS_DATETIME_TIME)) { + + // "in the morning at 7 oclock or 8 oclock" + methods.add(this::extractAmPmContext); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIME) && lastEntityType.equals(Constants.SYS_DATETIME_DATETIME)) { + + // "next week Mon 9am or Tue 1pm" + methods.add(config.getDatePeriodExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + + // "Monday 7-8 am or 9-10am" + methods.add(config.getDateExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + + // For alt entities that are all DatePeriod, no need to share context + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + + // "Tuesday or Wednesday morning" + methods.add(config.getDateExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + + // "Monday this week or next week" + methods.add(config.getDatePeriodExtractor()::extract); + + } + + return methods; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java new file mode 100644 index 000000000..8fbdd8b89 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java @@ -0,0 +1,292 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +public class BaseDateTimeExtractor implements IDateTimeExtractor { + private static final String SYS_NUM_INTEGER = com.microsoft.recognizers.text.number.Constants.SYS_NUM; + + private final IDateTimeExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATETIME; + } + + public BaseDateTimeExtractor(IDateTimeExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + tokens.addAll(mergeDateAndTime(input, reference)); + tokens.addAll(basicRegexMatch(input)); + tokens.addAll(timeOfTodayBefore(input, reference)); + tokens.addAll(timeOfTodayAfter(input, reference)); + tokens.addAll(specialTimeOfDate(input, reference)); + tokens.addAll(durationWithBeforeAndAfter(input, reference)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List durationWithBeforeAndAfter(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + + List ers = this.config.getDurationExtractor().extract(input, reference); + for (ExtractResult er : ers) { + // if it is a multiple duration and its type is equal to Date then skip it. + if (er.getData() != null && er.getData().toString() == Constants.MultipleDuration_Date) { + continue; + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getUnitRegex(), er.getText())).findFirst(); + if (!match.isPresent()) { + continue; + } + + ret = AgoLaterUtil.extractorDurationWithBeforeAndAfter(input, er, ret, this.config.getUtilityConfiguration()); + } + + return ret; + } + + public List specialTimeOfDate(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getDatePointExtractor().extract(input, reference); + + // handle "the end of the day" + for (ExtractResult er : ers) { + String beforeStr = input.substring(0, (er != null) ? er.getStart() : 0); + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSpecificEndOfRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(match.get().index, (er != null) ? er.getStart() + er.getLength() : 0)); + } else { + String afterStr = input.substring((er != null) ? er.getStart() + er.getLength() : 0); + + match = Arrays.stream(RegExpUtility.getMatches(this.config.getSpecificEndOfRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token( + (er != null) ? er.getStart() : 0, + ((er != null) ? er.getStart() + er.getLength() : 0) + ((match != null) ? match.get().index + match.get().length : 0))); + } + } + } + + // Handle "eod, end of day" + Match[] matches = RegExpUtility.getMatches(config.getUnspecificEndOfRegex(), input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + // Parses a specific time of today, tonight, this afternoon, like "seven this afternoon" + public List timeOfTodayAfter(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + + List ers = this.config.getTimePointExtractor().extract(input, reference); + + for (ExtractResult er : ers) { + String afterStr = input.substring(er.getStart() + er.getLength()); + + if (StringUtility.isNullOrEmpty(afterStr)) { + continue; //@here + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getTimeOfTodayAfterRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + int begin = er.getStart(); + int end = er.getStart() + er.getLength() + match.get().length; + ret.add(new Token(begin, end)); + } + } + + Match[] matches = RegExpUtility.getMatches(this.config.getSimpleTimeOfTodayAfterRegex(), input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + public List timeOfTodayBefore(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getTimePointExtractor().extract(input, reference); + for (ExtractResult er : ers) { + String beforeStr = input.substring(0, (er != null) ? er.getStart() : 0); + + // handle "this morningh at 7am" + ConditionalMatch innerMatch = RegexExtension.matchBegin(this.config.getTimeOfDayRegex(), er.getText(), true); + if (innerMatch.getSuccess()) { + beforeStr = input.substring(0, ((er != null) ? er.getStart() : 0) + innerMatch.getMatch().get().length); + } + + if (StringUtility.isNullOrEmpty(beforeStr)) { + continue; + } + + Match match = Arrays.stream(RegExpUtility.getMatches(this.config.getTimeOfTodayBeforeRegex(), beforeStr)).findFirst().orElse(null); + if (match != null) { + int begin = match.index; + int end = er.getStart() + er.getLength(); + ret.add(new Token(begin, end)); + } + } + + Match[] matches = RegExpUtility.getMatches(this.config.getSimpleTimeOfTodayBeforeRegex(), input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + // Match "now" + public List basicRegexMatch(String input) { + List ret = new ArrayList<>(); + input = input.trim().toLowerCase(); + + // Handle "now" + Match[] matches = RegExpUtility.getMatches(this.config.getNowRegex(), input); + + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + // Merge a Date entity and a Time entity, like "at 7 tomorrow" + public List mergeDateAndTime(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + List dateErs = this.config.getDatePointExtractor().extract(input, reference); + if (dateErs.size() == 0) { + return ret; + } + + List timeErs = this.config.getTimePointExtractor().extract(input, reference); + Match[] timeNumMatches = RegExpUtility.getMatches(config.getNumberAsTimeRegex(), input); + + if (timeErs.size() == 0 && timeNumMatches.length == 0) { + return ret; + } + + List ers = dateErs; + ers.addAll(timeErs); + + // handle cases which use numbers as time points + // only enabled in CalendarMode + if (this.config.getOptions().match(DateTimeOptions.CalendarMode)) { + List numErs = new ArrayList<>(); + for (Match timeNumMatch : timeNumMatches) { + ExtractResult node = new ExtractResult(timeNumMatch.index, timeNumMatch.length, timeNumMatch.value, SYS_NUM_INTEGER); + numErs.add(node); + } + ers.addAll(numErs); + } + + ers.sort(Comparator.comparingInt(erA -> erA.getStart())); + + int i = 0; + while (i < ers.size() - 1) { + int j = i + 1; + while (j < ers.size() && ers.get(i).isOverlap(ers.get(j))) { + j++; + } + if (j >= ers.size()) { + break; + } + + ExtractResult ersI = ers.get(i); + ExtractResult ersJ = ers.get(j); + if (ersI.getType() == Constants.SYS_DATETIME_DATE && ersJ.getType() == Constants.SYS_DATETIME_TIME || + ersI.getType() == Constants.SYS_DATETIME_TIME && ersJ.getType() == Constants.SYS_DATETIME_DATE || + ersI.getType() == Constants.SYS_DATETIME_DATE && ersJ.getType() == SYS_NUM_INTEGER) { + int middleBegin = ersI != null ? ersI.getStart() + ersI.getLength() : 0; + int middleEnd = ersJ != null ? ersJ.getStart() : 0; + if (middleBegin > middleEnd) { + i = j + 1; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(); + boolean valid = false; + // for cases like "tomorrow 3", "tomorrow at 3" + if (ersJ.getType() == SYS_NUM_INTEGER) { + Optional matches = Arrays.stream(RegExpUtility.getMatches(this.config.getDateNumberConnectorRegex(), input)).findFirst(); + if (StringUtility.isNullOrEmpty(middleStr) || matches.isPresent()) { + valid = true; + } + } else { + // For case like "3pm or later on monday" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSuffixAfterRegex(), middleStr)).findFirst(); + if (match.isPresent()) { + middleStr = middleStr.substring(match.get().index + match.get().length).trim(); + } + + if (!(match.isPresent() && middleStr.isEmpty())) { + if (this.config.isConnector(middleStr)) { + valid = true; + } + } + } + + if (valid) { + int begin = ersI.getStart(); + int end = ersJ.getStart() + ersJ.getLength(); + ret.add(new Token(begin, end)); + i = j + 1; + continue; + } + } + i = j; + } + + // Handle "in the afternoon" at the end of entity + for (int idx = 0; idx < ret.size(); idx++) { + Token idxToken = ret.get(idx); + String afterStr = input.substring(idxToken.getEnd()); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSuffixRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + ret.set(idx, new Token(idxToken.getStart(), idxToken.getEnd() + match.get().length)); + } + } + + // Handle "day" prefixes + for (int idx = 0; idx < ret.size(); idx++) { + Token idxToken = ret.get(idx); + String beforeStr = input.substring(0, idxToken.getStart()); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getUtilityConfiguration().getCommonDatePrefixRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + ret.set(idx, new Token(idxToken.getStart() - match.get().length, idxToken.getEnd())); + } + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java new file mode 100644 index 000000000..d4b8b7b5c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java @@ -0,0 +1,575 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseDateTimePeriodExtractor implements IDateTimeExtractor { + + private final IDateTimePeriodExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + + public BaseDateTimePeriodExtractor(IDateTimePeriodExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List tokens = new ArrayList<>(); + + // Date and time Extractions should be extracted from the text only once, and shared in the methods below, passed by value + List dateErs = config.getSingleDateExtractor().extract(input, reference); + List timeErs = config.getSingleTimeExtractor().extract(input, reference); + + tokens.addAll(matchSimpleCases(input, reference)); + tokens.addAll(mergeTwoTimePoints(input, reference, new ArrayList(dateErs), new ArrayList(timeErs))); + tokens.addAll(matchDuration(input, reference)); + tokens.addAll(matchTimeOfDay(input, reference, new ArrayList(dateErs))); + tokens.addAll(matchRelativeUnit(input)); + tokens.addAll(matchDateWithPeriodPrefix(input, reference, new ArrayList(dateErs))); + tokens.addAll(mergeDateWithTimePeriodSuffix(input, new ArrayList(dateErs), new ArrayList(timeErs))); + + List ers = Token.mergeAllTokens(tokens, input, getExtractorName()); + + if (config.getOptions().match(DateTimeOptions.EnablePreview)) { + ers = TimeZoneUtility.mergeTimeZones(ers, config.getTimeZoneExtractor().extract(input, reference), input); + } + + return ers; + } + + private List matchSimpleCases(String input, LocalDateTime reference) { + List results = new ArrayList<>(); + + for (Pattern regex : config.getSimpleCasesRegex()) { + Match[] matches = RegExpUtility.getMatches(regex, input); + for (Match match : matches) { + // Is there a date before it? + boolean hasBeforeDate = false; + String beforeStr = input.substring(0, match.index); + if (!StringUtility.isNullOrEmpty(beforeStr)) { + List ers = config.getSingleDateExtractor().extract(beforeStr, reference); + if (ers.size() > 0) { + ExtractResult er = ers.get(ers.size() - 1); + int begin = er.getStart(); + String middleStr = beforeStr.substring(begin + er.getLength()).trim().toLowerCase(); + if (StringUtility.isNullOrEmpty(middleStr) || RegexExtension.isExactMatch(config.getPrepositionRegex(), middleStr, true)) { + results.add(new Token(begin, match.index + match.length)); + hasBeforeDate = true; + } + } + } + + String followedStr = input.substring(match.index + match.length); + if (!StringUtility.isNullOrEmpty(followedStr) && !hasBeforeDate) { + // Is it followed by a date? + List ers = config.getSingleDateExtractor().extract(followedStr, reference); + if (ers.size() > 0) { + ExtractResult er = ers.get(0); + int begin = er.getStart(); + int end = er.getStart() + er.getLength(); + String middleStr = followedStr.substring(0, begin).trim().toLowerCase(); + if (StringUtility.isNullOrEmpty(middleStr) || RegexExtension.isExactMatch(config.getPrepositionRegex(), middleStr, true)) { + results.add(new Token(match.index, match.index + match.length + end)); + } + } + } + } + } + + return results; + } + + private List mergeTwoTimePoints(String input, LocalDateTime reference, List dateErs, List timeErs) { + + List results = new ArrayList<>(); + List dateTimeErs = config.getSingleDateTimeExtractor().extract(input, reference); + List timePoints = new ArrayList<>(); + + // Handle the overlap problem + int j = 0; + for (ExtractResult er : dateTimeErs) { + timePoints.add(er); + + while (j < timeErs.size() && timeErs.get(j).getStart() + timeErs.get(j).getLength() < er.getStart()) { + timePoints.add(timeErs.get(j)); + j++; + } + + while (j < timeErs.size() && timeErs.get(j).isOverlap(er)) { + j++; + } + } + + for (; j < timeErs.size(); j++) { + timePoints.add(timeErs.get(j)); + } + + timePoints.sort(Comparator.comparingInt(arg -> arg.getStart())); + + // Merge "{TimePoint} to {TimePoint}", "between {TimePoint} and {TimePoint}" + int idx = 0; + while (idx < timePoints.size() - 1) { + // If both ends are Time. then this is a TimePeriod, not a DateTimePeriod + if (timePoints.get(idx).getType().equals(Constants.SYS_DATETIME_TIME) && timePoints.get(idx + 1).getType().equals(Constants.SYS_DATETIME_TIME)) { + idx++; + continue; + } + + int middleBegin = timePoints.get(idx).getStart() + timePoints.get(idx).getLength(); + int middleEnd = timePoints.get(idx + 1).getStart(); + + String middleStr = input.substring(middleBegin, middleEnd).trim(); + + // Handle "{TimePoint} to {TimePoint}" + if (RegexExtension.isExactMatch(config.getTillRegex(), middleStr, true)) { + int periodBegin = timePoints.get(idx).getStart(); + int periodEnd = timePoints.get(idx + 1).getStart() + timePoints.get(idx + 1).getLength(); + + // Handle "from" + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex fromIndex = config.getFromTokenIndex(beforeStr); + ResultIndex betweenIndex = config.getBetweenTokenIndex(beforeStr); + if (fromIndex.getResult()) { + periodBegin = fromIndex.getIndex(); + } else if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + } + + results.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + + // Handle "between {TimePoint} and {TimePoint}" + if (config.hasConnectorToken(middleStr)) { + int periodBegin = timePoints.get(idx).getStart(); + int periodEnd = timePoints.get(idx + 1).getStart() + timePoints.get(idx + 1).getLength(); + + // Handle "between" + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex betweenIndex = config.getBetweenTokenIndex(beforeStr); + if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + results.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + } + idx++; + } + + // Regarding the pharse as-- {Date} {TimePeriod}, like "2015-9-23 1pm to 4" + // Or {TimePeriod} on {Date}, like "1:30 to 4 on 2015-9-23" + List timePeriodErs = config.getTimePeriodExtractor().extract(input, reference); + dateErs.addAll(timePeriodErs); + + dateErs.sort(Comparator.comparingInt(arg -> arg.getStart())); + + for (idx = 0; idx < dateErs.size() - 1; idx++) { + if (dateErs.get(idx).getType().equals(dateErs.get(idx + 1).getType())) { + continue; + } + + int midBegin = dateErs.get(idx).getStart() + dateErs.get(idx).getLength(); + int midEnd = dateErs.get(idx + 1).getStart(); + + if (midEnd - midBegin > 0) { + String midStr = input.substring(midBegin, midEnd); + if (StringUtility.isNullOrWhiteSpace(midStr) || StringUtility.trimStart(midStr).startsWith(config.getTokenBeforeDate())) { + // Extend date extraction for cases like "Monday evening next week" + String extendedStr = dateErs.get(idx).getText() + input.substring(dateErs.get(idx + 1).getStart() + dateErs.get(idx + 1).getLength()); + Optional extendedDateEr = config.getSingleDateExtractor().extract(extendedStr).stream().findFirst(); + int offset = 0; + + if (extendedDateEr.isPresent() && extendedDateEr.get().getStart() == 0) { + offset = extendedDateEr.get().getLength() - dateErs.get(idx).getLength(); + } + + results.add(new Token(dateErs.get(idx).getStart(), offset + dateErs.get(idx + 1).getStart() + dateErs.get(idx + 1).getLength())); + idx += 2; + } + } + } + + return results; + } + + //TODO: this can be abstracted with the similar method in BaseDatePeriodExtractor + private List matchDuration(String input, LocalDateTime reference) { + List results = new ArrayList<>(); + + List durations = new ArrayList<>(); + List durationErs = config.getDurationExtractor().extract(input, reference); + + for (ExtractResult durationEr : durationErs) { + Optional match = match(config.getTimeUnitRegex(), durationEr.getText()); + if (match.isPresent()) { + durations.add(new Token(durationEr.getStart(), durationEr.getStart() + durationEr.getLength())); + } + } + + for (Token duration : durations) { + String beforeStr = input.substring(0, duration.getStart()).toLowerCase(); + String afterStr = input.substring(duration.getStart() + duration.getLength()); + + if (StringUtility.isNullOrWhiteSpace(beforeStr) && StringUtility.isNullOrWhiteSpace(afterStr)) { + continue; + } + + // within (the) (next) "Seconds/Minutes/Hours" should be handled as datetimeRange here + // within (the) (next) XX days/months/years + "Seconds/Minutes/Hours" should also be handled as datetimeRange here + Optional match = match(config.getWithinNextPrefixRegex(), beforeStr); + if (matchPrefixRegexInSegment(beforeStr, match)) { + int startToken = match.get().index; + int durationLength = duration.getStart() + duration.getLength(); + match = match(config.getTimeUnitRegex(), input.substring(duration.getStart(), durationLength)); + if (match.isPresent()) { + results.add(new Token(startToken, duration.getEnd())); + continue; + } + } + + int index = -1; + + match = match(config.getPastPrefixRegex(), beforeStr); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(beforeStr.substring(match.get().index + match.get().length))) { + index = match.get().index; + } + + if (index < 0) { + match = match(config.getNextPrefixRegex(), beforeStr); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(beforeStr.substring(match.get().index + match.get().length))) { + index = match.get().index; + } + } + + if (index >= 0) { + + String prefix = beforeStr.substring(0, index).trim(); + String durationText = input.substring(duration.getStart(), duration.getStart() + duration.getLength()); + List numbersInPrefix = config.getCardinalExtractor().extract(prefix); + List numbersInDuration = config.getCardinalExtractor().extract(durationText); + + // Cases like "2 upcoming days", should be supported here + // Cases like "2 upcoming 3 days" is invalid, only extract "upcoming 3 days" by default + if (!numbersInPrefix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult lastNumber = numbersInPrefix.stream() + .sorted(Comparator.comparingInt(x -> x.getStart() + x.getLength())) + .reduce((acc, item) -> item).orElse(null); + + // Prefix should ends with the last number + if (lastNumber.getStart() + lastNumber.getLength() == prefix.length()) { + results.add(new Token(lastNumber.getStart(), duration.getEnd())); + } + + } else { + results.add(new Token(index, duration.getEnd())); + } + + continue; + } + + Optional matchDateUnit = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), afterStr)).findFirst(); + if (!matchDateUnit.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPastPrefixRegex(), afterStr)).findFirst(); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(duration.getStart(), duration.getStart() + duration.getLength() + match.get().index + match.get().length)); + continue; + } + + match = Arrays.stream(RegExpUtility.getMatches(config.getNextPrefixRegex(), afterStr)).findFirst(); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(duration.getStart(), duration.getStart() + duration.getLength() + match.get().index + match.get().length)); + continue; + } + + match = Arrays.stream(RegExpUtility.getMatches(config.getFutureSuffixRegex(), afterStr)).findFirst(); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(duration.getStart(), duration.getStart() + duration.getLength() + match.get().index + match.get().length)); + } + } + } + + return results; + } + + private Optional match(Pattern regex, String input) { + return Arrays.stream(RegExpUtility.getMatches(regex, input)).findFirst(); + } + + private boolean matchPrefixRegexInSegment(String beforeStr, Optional match) { + return match.isPresent() && StringUtility.isNullOrWhiteSpace(beforeStr.substring(match.get().index + match.get().length)); + } + + private List matchTimeOfDay(String input, LocalDateTime reference, List dateErs) { + + List results = new ArrayList<>(); + Match[] matches = RegExpUtility.getMatches(config.getSpecificTimeOfDayRegex(), input); + for (Match match : matches) { + results.add(new Token(match.index, match.index + match.length)); + } + + // Date followed by morning, afternoon or morning, afternoon followed by Date + if (dateErs.size() == 0) { + return results; + } + + for (ExtractResult er : dateErs) { + String afterStr = input.substring(er.getStart() + er.getLength()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), afterStr)).findFirst(); + + if (match.isPresent()) { + // For cases like "Friday afternoon between 1PM and 4 PM" which "Friday afternoon" need to be extracted first + if (StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + int start = er.getStart(); + int end = er.getStart() + er.getLength() + + match.get().getGroup(Constants.TimeOfDayGroupName).index + + match.get().getGroup(Constants.TimeOfDayGroupName).length; + + results.add(new Token(start, end)); + continue; + } + + String connectorStr = afterStr.substring(0, match.get().index); + + // Trim here is set to false as the Regex might catch white spaces before or after the text + boolean isMatch = RegexExtension.isExactMatch(config.getMiddlePauseRegex(), connectorStr, false); + if (isMatch) { + String suffix = StringUtility.trimStart(afterStr.substring(match.get().index + match.get().length)); + + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(config.getGeneralEndingRegex(), suffix)).findFirst(); + + if (endingMatch.isPresent()) { + results.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().index + match.get().length)); + } + } + } + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getAmDescRegex(), afterStr)).findFirst(); + } + + if (!match.isPresent() || !StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPmDescRegex(), afterStr)).findFirst(); + } + + if (match.isPresent()) { + if (StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().index + match.get().length)); + } else { + String connectorStr = afterStr.substring(0, match.get().index); + // Trim here is set to false as the Regex might catch white spaces before or after the text + if (RegexExtension.isExactMatch(config.getMiddlePauseRegex(), connectorStr, false)) { + String suffix = afterStr.substring(match.get().index + match.get().length).replaceAll("^\\s+", ""); + + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(config.getGeneralEndingRegex(), suffix)).findFirst(); + if (endingMatch.isPresent()) { + results.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().index + match.get().length)); + } + } + } + } + + String prefixStr = input.substring(0, er.getStart()); + + match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), prefixStr)).findFirst(); + if (match.isPresent()) { + if (StringUtility.isNullOrWhiteSpace(prefixStr.substring(match.get().index + match.get().length))) { + String midStr = input.substring(match.get().index + match.get().length, er.getStart()); + if (!StringUtility.isNullOrEmpty(midStr) && StringUtility.isNullOrWhiteSpace(midStr)) { + results.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } else { + String connectorStr = prefixStr.substring(match.get().index + match.get().length); + + // Trim here is set to false as the Regex might catch white spaces before or after the text + if (RegexExtension.isExactMatch(config.getMiddlePauseRegex(), connectorStr, false)) { + String suffix = StringUtility.trimStart(input.substring(er.getStart() + er.getLength())); + + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(config.getGeneralEndingRegex(), suffix)).findFirst(); + if (endingMatch.isPresent()) { + results.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } + } + } + } + + // Check whether there are adjacent time period strings, before or after + for (Token result : results.toArray(new Token[0])) { + // Try to extract a time period in before-string + if (result.getStart() > 0) { + String beforeStr = input.substring(0, result.getStart()); + if (!StringUtility.isNullOrEmpty(beforeStr)) { + List timeErs = config.getTimePeriodExtractor().extract(beforeStr); + if (timeErs.size() > 0) { + for (ExtractResult timeEr : timeErs) { + String midStr = beforeStr.substring(timeEr.getStart() + timeEr.getLength()); + if (StringUtility.isNullOrWhiteSpace(midStr)) { + results.add(new Token(timeEr.getStart(), timeEr.getStart() + timeEr.getLength() + midStr.length() + result.getLength())); + } + } + } + } + } + + // Try to extract a time period in after-string + if (result.getStart() + result.getLength() <= input.length()) { + String afterStr = input.substring(result.getStart() + result.getLength()); + if (!StringUtility.isNullOrEmpty(afterStr)) { + List timeErs = config.getTimePeriodExtractor().extract(afterStr); + for (ExtractResult timeEr: timeErs) { + String midStr = afterStr.substring(0, timeEr.getStart()); + if (StringUtility.isNullOrWhiteSpace(midStr)) { + results.add(new Token(result.getStart(), result.getStart() + result.getLength() + midStr.length() + timeEr.getLength())); + } + } + + } + } + } + + return results; + } + + private List matchRelativeUnit(String input) { + List results = new ArrayList(); + + Match[] matches = RegExpUtility.getMatches(config.getRelativeTimeUnitRegex(), input); + if (matches.length == 0) { + matches = RegExpUtility.getMatches(config.getRestOfDateTimeRegex(), input); + } + + for (Match match : matches) { + results.add(new Token(match.index, match.index + match.length)); + } + + return results; + } + + private List matchDateWithPeriodPrefix(String input, LocalDateTime reference, List dateErs) { + List results = new ArrayList(); + + for (ExtractResult dateEr : dateErs) { + int dateStrEnd = dateEr.getStart() + dateEr.getLength(); + String beforeStr = input.substring(0, dateEr.getStart()).trim(); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPrefixDayRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + results.add(new Token(match.get().index, dateStrEnd)); + } + } + + return results; + } + + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + private List mergeDateWithTimePeriodSuffix(String input, List dateErs, List timeErs) { + List results = new ArrayList(); + + if (dateErs.isEmpty()) { + return results; + } + + if (timeErs.isEmpty()) { + return results; + } + + List ers = new ArrayList<>(); + ers.addAll(dateErs); + ers.addAll(timeErs); + + ers.sort(Comparator.comparingInt(arg -> arg.getStart())); + + int i = 0; + while (i < ers.size() - 1) { + int j = i + 1; + while (j < ers.size() && ers.get(i).isOverlap(ers.get(j))) { + j++; + } + + if (j >= ers.size()) { + break; + } + + if (ers.get(i).getType().equals(Constants.SYS_DATETIME_DATE) && ers.get(j).getType().equals(Constants.SYS_DATETIME_TIME)) { + int middleBegin = ers.get(i).getStart() + ers.get(i).getLength(); + int middleEnd = ers.get(j).getStart(); + if (middleBegin > middleEnd) { + i = j + 1; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(); + if (isValidConnectorForDateAndTimePeriod(middleStr)) { + int begin = ers.get(i).getStart(); + int end = ers.get(j).getStart() + ers.get(j).getLength(); + results.add(new Token(begin, end)); + } + + i = j + 1; + continue; + } + i = j; + } + + // Handle "in the afternoon" at the end of entity + for (int idx = 0; idx < results.size(); idx++) { + String afterStr = input.substring(results.get(idx).getEnd()); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSuffixRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + Token oldToken = results.get(idx); + results.set(idx, new Token(oldToken.getStart(), oldToken.getEnd() + match.get().length)); + } + } + + return results; + } + + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + // Valid connector in English for Before include: "before", "no later than", "in advance of", "prior to", "earlier than", "sooner than", "by", "till", "until"... + // Valid connector in English for After include: "after", "later than" + private boolean isValidConnectorForDateAndTimePeriod(String text) { + + List beforeAfterRegexes = new ArrayList<>(); + beforeAfterRegexes.add(config.getBeforeRegex()); + beforeAfterRegexes.add(config.getAfterRegex()); + + for (Pattern regex : beforeAfterRegexes) { + if (RegexExtension.isExactMatch(regex, text, true)) { + return true; + } + } + + return false; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java new file mode 100644 index 000000000..949aa4c1e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java @@ -0,0 +1,281 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DurationParsingUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BaseDurationExtractor implements IDateTimeExtractor { + + private final IDurationExtractorConfiguration config; + private final boolean merge; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DURATION; + } + + public BaseDurationExtractor(IDurationExtractorConfiguration config) { + this(config, true); + } + + public BaseDurationExtractor(IDurationExtractorConfiguration config, boolean merge) { + this.config = config; + this.merge = merge; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List numberWithUnitTokens = numberWithUnit(input); + + tokens.addAll(numberWithUnitTokens); + tokens.addAll(numberWithUnitAndSuffix(input, numberWithUnitTokens)); + tokens.addAll(implicitDuration(input)); + + List result = Token.mergeAllTokens(tokens, input, getExtractorName()); + + // First MergeMultipleDuration then ResolveMoreThanOrLessThanPrefix so cases like "more than 4 days and less than 1 week" will not be merged into one "multipleDuration" + if (merge) { + result = mergeMultipleDuration(input, result); + } + + result = tagInequalityPrefix(input, result); + + return result; + } + + private List tagInequalityPrefix(String input, List result) { + Stream resultStream = result.stream().map(er -> { + String beforeString = input.substring(0, er.getStart()); + boolean isInequalityPrefixMatched = false; + + ConditionalMatch match = RegexExtension.matchEnd(this.config.getMoreThanRegex(), beforeString, true); + + // The second condition is necessary so for "1 week" in "more than 4 days and less than 1 week", it will not be tagged incorrectly as "more than" + if (match.getSuccess()) { + er.setData(Constants.MORE_THAN_MOD); + isInequalityPrefixMatched = true; + } + + if (!isInequalityPrefixMatched) { + match = RegexExtension.matchEnd(this.config.getLessThanRegex(), beforeString, true); + + if (match.getSuccess()) { + er.setData(Constants.LESS_THAN_MOD); + isInequalityPrefixMatched = true; + } + } + + if (isInequalityPrefixMatched) { + int length = er.getLength() + er.getStart() - match.getMatch().get().index; + int start = match.getMatch().get().index; + String text = input.substring(start, start + length); + er.setStart(start); + er.setLength(length); + er.setText(text); + } + + return er; + }); + return resultStream.collect(Collectors.toList()); + } + + private List mergeMultipleDuration(String input, List extractResults) { + if (extractResults.size() <= 1) { + return extractResults; + } + + ImmutableMap unitMap = config.getUnitMap(); + ImmutableMap unitValueMap = config.getUnitValueMap(); + Pattern unitRegex = config.getDurationUnitRegex(); + + List result = new ArrayList<>(); + + int firstExtractionIndex = 0; + int timeUnit = 0; + int totalUnit = 0; + + while (firstExtractionIndex < extractResults.size()) { + String currentUnit = null; + Optional unitMatch = Arrays.stream(RegExpUtility.getMatches(unitRegex, extractResults.get(firstExtractionIndex).getText())).findFirst(); + + if (unitMatch.isPresent() && unitMap.containsKey(unitMatch.get().getGroup("unit").value)) { + currentUnit = unitMatch.get().getGroup("unit").value; + totalUnit++; + if (DurationParsingUtil.isTimeDurationUnit(unitMap.get(currentUnit))) { + timeUnit++; + } + } + + if (StringUtility.isNullOrEmpty(currentUnit)) { + firstExtractionIndex++; + continue; + } + + int secondExtractionIndex = firstExtractionIndex + 1; + while (secondExtractionIndex < extractResults.size()) { + boolean valid = false; + int midStrBegin = extractResults.get(secondExtractionIndex - 1).getStart() + extractResults.get(secondExtractionIndex - 1).getLength(); + int midStrEnd = extractResults.get(secondExtractionIndex).getStart(); + String midStr = input.substring(midStrBegin, midStrEnd); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getDurationConnectorRegex(), midStr)).findFirst(); + + if (match.isPresent()) { + unitMatch = Arrays.stream(RegExpUtility.getMatches(unitRegex, extractResults.get(secondExtractionIndex).getText())).findFirst(); + + if (unitMatch.isPresent() && unitMap.containsKey(unitMatch.get().getGroup("unit").value)) { + String nextUnitStr = unitMatch.get().getGroup("unit").value; + + if (unitValueMap.get(nextUnitStr) != unitValueMap.get(currentUnit)) { + valid = true; + + if (unitValueMap.get(nextUnitStr) < unitValueMap.get(currentUnit)) { + currentUnit = nextUnitStr; + } + } + + totalUnit++; + + if (DurationParsingUtil.isTimeDurationUnit(unitMap.get(nextUnitStr))) { + timeUnit++; + } + } + } + + if (!valid) { + break; + } + + secondExtractionIndex++; + } + + if (secondExtractionIndex - 1 > firstExtractionIndex) { + int start = extractResults.get(firstExtractionIndex).getStart(); + int length = extractResults.get(secondExtractionIndex - 1).getStart() + extractResults.get(secondExtractionIndex - 1).getLength() - start; + String text = input.substring(start, start + length); + String rType = extractResults.get(firstExtractionIndex).getType(); + ExtractResult node = new ExtractResult(start, length, text, rType, null); + + // add multiple duration type to extract result + String type = null; + + if (timeUnit == totalUnit) { + type = Constants.MultipleDuration_Time; + } else if (timeUnit == 0) { + type = Constants.MultipleDuration_Date; + } else { + type = Constants.MultipleDuration_DateTime; + } + + node.setData(type); + result.add(node); + + timeUnit = 0; + totalUnit = 0; + + } else { + result.add(extractResults.get(firstExtractionIndex)); + } + + firstExtractionIndex = secondExtractionIndex; + } + + return result; + } + + // handle cases that don't contain nubmer + private Collection implicitDuration(String text) { + Collection result = new ArrayList<>(); + + // handle "all day", "all year" + result.addAll(getTokenFromRegex(config.getAllRegex(), text)); + + // handle "half day", "half year" + result.addAll(getTokenFromRegex(config.getHalfRegex(), text)); + + // handle "next day", "last year" + result.addAll(getTokenFromRegex(config.getRelativeDurationUnitRegex(), text)); + + // handle "during/for the day/week/month/year" + if (config.getOptions().match(DateTimeOptions.CalendarMode)) { + result.addAll(getTokenFromRegex(config.getDuringRegex(), text)); + } + + return result; + } + + // simple cases made by a number followed an unit + private List numberWithUnit(String text) { + List result = new ArrayList<>(); + List ers = this.config.getCardinalExtractor().extract(text); + for (ExtractResult er : ers) { + String afterStr = text.substring(er.getStart() + er.getLength()); + ConditionalMatch match = RegexExtension.matchBegin(this.config.getFollowedUnit(), afterStr, true); + if (match.getSuccess() && match.getMatch().get().index == 0) { + result.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.getMatch().get().length)); + } + } + + // handle "3hrs" + result.addAll(this.getTokenFromRegex(this.config.getNumberCombinedWithUnit(), text)); + + // handle "an hour" + result.addAll(this.getTokenFromRegex(this.config.getAnUnitRegex(), text)); + + // handle "few" related cases + result.addAll(this.getTokenFromRegex(this.config.getInexactNumberUnitRegex(), text)); + + return result; + } + + private Collection getTokenFromRegex(Pattern pattern, String text) { + Collection result = new ArrayList<>(); + + for (Match match : RegExpUtility.getMatches(pattern, text)) { + result.add(new Token(match.index, match.index + match.length)); + } + + return result; + } + + // handle cases look like: {number} {unit}? and {an|a} {half|quarter} {unit}? + // define the part "and {an|a} {half|quarter}" as Suffix + private Collection numberWithUnitAndSuffix(String text, Collection tokens) { + Collection result = new ArrayList<>(); + + for (Token token : tokens) { + String afterStr = text.substring(token.getStart() + token.getLength()); + ConditionalMatch match = RegexExtension.matchBegin(this.config.getSuffixAndRegex(), afterStr, true); + if (match.getSuccess() && match.getMatch().get().index == 0) { + result.add(new Token(token.getStart(), token.getStart() + token.getLength() + match.getMatch().get().length)); + } + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java new file mode 100644 index 000000000..d901688f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java @@ -0,0 +1,60 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class BaseHolidayExtractor implements IDateTimeExtractor { + + private final IHolidayExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATE; + } + + public BaseHolidayExtractor(IHolidayExtractorConfiguration config) { + this.config = config; + } + + @Override + public final List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + tokens.addAll(holidayMatch(input)); + List ers = Token.mergeAllTokens(tokens, input, getExtractorName()); + for (ExtractResult er : ers) { + Metadata metadata = new Metadata() { + { + setIsHoliday(true); + } + }; + er.setMetadata(metadata); + } + return ers; + } + + @Override + public final List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List holidayMatch(String text) { + List ret = new ArrayList<>(); + for (Pattern regex : this.config.getHolidayRegexes()) { + Match[] matches = RegExpUtility.getMatches(regex, text); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java new file mode 100644 index 000000000..eb765c993 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java @@ -0,0 +1,361 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ProcessedSuperfluousWords; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.MatchingUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.matcher.MatchResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +public class BaseMergedDateTimeExtractor implements IDateTimeExtractor { + + private final IMergedExtractorConfiguration config; + + @Override + public String getExtractorName() { + return ""; + } + + public BaseMergedDateTimeExtractor(IMergedExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List ret = new ArrayList<>(); + String originInput = input; + Iterable> superfluousWordMatches = null; + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + ProcessedSuperfluousWords processedSuperfluousWords = MatchingUtil.preProcessTextRemoveSuperfluousWords(input, this.config.getSuperfluousWordMatcher()); + input = processedSuperfluousWords.getText(); + superfluousWordMatches = processedSuperfluousWords.getSuperfluousWordMatches(); + } + + // The order is important, since there is a problem in merging + addTo(ret, this.config.getDateExtractor().extract(input, reference), input); + addTo(ret, this.config.getTimeExtractor().extract(input, reference), input); + addTo(ret, this.config.getDatePeriodExtractor().extract(input, reference), input); + addTo(ret, this.config.getDurationExtractor().extract(input, reference), input); + addTo(ret, this.config.getTimePeriodExtractor().extract(input, reference), input); + addTo(ret, this.config.getDateTimePeriodExtractor().extract(input, reference), input); + addTo(ret, this.config.getDateTimeExtractor().extract(input, reference), input); + addTo(ret, this.config.getSetExtractor().extract(input, reference), input); + addTo(ret, this.config.getHolidayExtractor().extract(input, reference), input); + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + addTo(ret, this.config.getTimeZoneExtractor().extract(input, reference), input); + ret = this.config.getTimeZoneExtractor().removeAmbiguousTimezone(ret); + } + + // This should be at the end since if need the extractor to determine the previous text contains time or not + addTo(ret, numberEndingRegexMatch(input, ret), input); + + // modify time entity to an alternative DateTime expression if it follows a DateTime entity + if (this.config.getOptions().match(DateTimeOptions.ExtendedTypes)) { + ret = this.config.getDateTimeAltExtractor().extract(ret, input, reference); + } + + ret = filterUnspecificDatePeriod(ret, input); + + // Remove common ambiguous cases + ret = filterAmbiguity(ret, input); + + ret = addMod(ret, input); + + // filtering + if (this.config.getOptions().match(DateTimeOptions.CalendarMode)) { + checkCalendarFilterList(ret, input); + } + + ret.sort(Comparator.comparingInt(r -> r.getStart())); + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + ret = MatchingUtil.posProcessExtractionRecoverSuperfluousWords(ret, superfluousWordMatches, originInput); + } + + return ret; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List filterAmbiguity(List extractResults, String input) { + if (config.getAmbiguityFiltersDict() != null) { + for (Pair pair : config.getAmbiguityFiltersDict()) { + final Pattern key = pair.getValue0(); + final Pattern value = pair.getValue1(); + + for (ExtractResult extractResult : extractResults) { + Optional keyMatch = Arrays.stream(RegExpUtility.getMatches(key, extractResult.getText())).findFirst(); + if (keyMatch.isPresent()) { + final Match[] matches = RegExpUtility.getMatches(value, input); + extractResults = extractResults.stream() + .filter(er -> Arrays.stream(matches).noneMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) + .collect(Collectors.toList()); + } + } + } + } + + return extractResults; + } + + private void addTo(List dst, List src, String text) { + for (ExtractResult result : src) { + if (config.getOptions().match(DateTimeOptions.SkipFromToMerge)) { + if (shouldSkipFromToMerge(result)) { + continue; + } + } + + boolean isFound = false; + List overlapIndexes = new ArrayList<>(); + int firstIndex = -1; + for (int i = 0; i < dst.size(); i++) { + if (dst.get(i).isOverlap(result)) { + isFound = true; + if (dst.get(i).isCover(result)) { + if (firstIndex == -1) { + firstIndex = i; + } + + overlapIndexes.add(i); + } else { + break; + } + } + } + + if (!isFound) { + dst.add(result); + } else if (overlapIndexes.size() > 0) { + List tempDst = new ArrayList<>(); + for (int i = 0; i < dst.size(); i++) { + if (!overlapIndexes.contains(i)) { + tempDst.add(dst.get(i)); + } + } + + // insert at the first overlap occurrence to keep the order + tempDst.add(firstIndex, result); + dst.clear(); + dst.addAll(tempDst); + } + + dst.sort(Comparator.comparingInt(a -> a.getStart())); + } + } + + private boolean shouldSkipFromToMerge(ExtractResult er) { + return Arrays.stream(RegExpUtility.getMatches(config.getFromToRegex(), er.getText())).findFirst().isPresent(); + } + + private List numberEndingRegexMatch(String text, List extractResults) { + List tokens = new ArrayList<>(); + + for (ExtractResult extractResult : extractResults) { + if (extractResult.getType().equals(Constants.SYS_DATETIME_TIME) || extractResult.getType().equals(Constants.SYS_DATETIME_DATETIME)) { + String stringAfter = text.substring(extractResult.getStart() + extractResult.getLength()); + Pattern numberEndingPattern = this.config.getNumberEndingPattern(); + Optional match = Arrays.stream(RegExpUtility.getMatches(numberEndingPattern, stringAfter)).findFirst(); + if (match.isPresent()) { + MatchGroup newTime = match.get().getGroup("newTime"); + List numRes = this.config.getIntegerExtractor().extract(newTime.value); + if (numRes.size() == 0) { + continue; + } + + int startPosition = extractResult.getStart() + extractResult.getLength() + newTime.index; + tokens.add(new Token(startPosition, startPosition + newTime.length)); + } + + } + } + + return Token.mergeAllTokens(tokens, text, Constants.SYS_DATETIME_TIME); + } + + private List filterUnspecificDatePeriod(List ers, String text) { + ers.removeIf(er -> Arrays.stream(RegExpUtility.getMatches(config.getUnspecificDatePeriodRegex(), er.getText())).findFirst().isPresent()); + return ers; + } + + private List addMod(List ers, String text) { + int index = 0; + + for (ExtractResult er : ers.toArray(new ExtractResult[0])) { + MergeModifierResult modifiedToken = tryMergeModifierToken(er, config.getBeforeRegex(), text); + + if (!modifiedToken.result) { + modifiedToken = tryMergeModifierToken(er, config.getAfterRegex(), text); + } + + if (!modifiedToken.result) { + // SinceRegex in English contains the term "from" which is potentially ambiguous with ranges in the form "from X to Y" + modifiedToken = tryMergeModifierToken(er, config.getSinceRegex(), text, true); + } + + if (!modifiedToken.result) { + modifiedToken = tryMergeModifierToken(er, config.getAroundRegex(), text); + } + + ers.set(index, modifiedToken.er); + + final ExtractResult newEr = modifiedToken.er; + + if (newEr.getType().equals(Constants.SYS_DATETIME_DATEPERIOD) || newEr.getType().equals(Constants.SYS_DATETIME_DATE) || + newEr.getType().equals(Constants.SYS_DATETIME_TIME)) { + + // 2012 or after/above, 3 pm or later + String afterStr = text.substring(newEr.getStart() + newEr.getLength()).toLowerCase(); + + ConditionalMatch match = RegexExtension.matchBegin(config.getSuffixAfterRegex(), StringUtility.trimStart(afterStr), true); + + if (match.getSuccess()) { + boolean isFollowedByOtherEntity = true; + + if (match.getMatch().get().length == afterStr.trim().length()) { + isFollowedByOtherEntity = false; + + } else { + String nextStr = afterStr.trim().substring(match.getMatch().get().length).trim(); + ExtractResult nextEr = ers.stream().filter(t -> t.getStart() > newEr.getStart()).findFirst().orElse(null); + + if (nextEr == null || !nextStr.startsWith(nextEr.getText())) { + isFollowedByOtherEntity = false; + } + } + + if (!isFollowedByOtherEntity) { + int modLength = match.getMatch().get().length + afterStr.indexOf(match.getMatch().get().value); + int length = newEr.getLength() + modLength; + String newText = text.substring(newEr.getStart(), newEr.getStart() + length); + + er.setMetadata(assignModMetadata(er.getMetadata())); + + ers.set(index, new ExtractResult(er.getStart(), length, newText, er.getType(), er.getData(), er.getMetadata())); + } + } + } + + index++; + } + + return ers; + } + + private MergeModifierResult tryMergeModifierToken(ExtractResult er, Pattern tokenRegex, String text) { + return tryMergeModifierToken(er, tokenRegex, text, false); + } + + private MergeModifierResult tryMergeModifierToken(ExtractResult er, Pattern tokenRegex, String text, boolean potentialAmbiguity) { + String beforeStr = text.substring(0, er.getStart()).toLowerCase(); + + // Avoid adding mod for ambiguity cases, such as "from" in "from ... to ..." should not add mod + if (potentialAmbiguity && config.getAmbiguousRangeModifierPrefix() != null && + Arrays.stream(RegExpUtility.getMatches(config.getAmbiguousRangeModifierPrefix(), text)).findFirst().isPresent()) { + final Match[] matches = RegExpUtility.getMatches(config.getPotentialAmbiguousRangeRegex(), text); + if (Arrays.stream(matches).anyMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) { + return new MergeModifierResult(false, er); + } + } + + ResultIndex result = hasTokenIndex(StringUtility.trimEnd(beforeStr), tokenRegex); + if (result.getResult()) { + int modLength = beforeStr.length() - result.getIndex(); + int length = er.getLength() + modLength; + int start = er.getStart() - modLength; + String newText = text.substring(start, start + length); + + er.setText(newText); + er.setLength(length); + er.setStart(start); + + er.setMetadata(assignModMetadata(er.getMetadata())); + + return new MergeModifierResult(true, er); + } + + return new MergeModifierResult(false, er); + } + + private Metadata assignModMetadata(Metadata metadata) { + + if (metadata == null) { + metadata = new Metadata() { + { + setHasMod(true); + } + }; + } else { + metadata.setHasMod(true); + } + + return metadata; + } + + private ResultIndex hasTokenIndex(String text, Pattern pattern) { + Match[] matches = RegExpUtility.getMatches(pattern, text); + + // Support cases has two or more specific tokens + // For example, "show me sales after 2010 and before 2018 or before 2000" + // When extract "before 2000", we need the second "before" which will be matched in the second Regex match + for (Match match : matches) { + if (StringUtility.isNullOrWhiteSpace(text.substring(match.index + match.length))) { + return new ResultIndex(true, match.index); + } + } + + return new ResultIndex(false, -1); + } + + private void checkCalendarFilterList(List ers, String text) { + List shallowCopy = new ArrayList<>(ers); + Collections.reverse(shallowCopy); + for (ExtractResult er : shallowCopy) { + for (Pattern negRegex : this.config.getFilterWordRegexList()) { + Optional match = Arrays.stream(RegExpUtility.getMatches(negRegex, er.getText())).findFirst(); + if (match.isPresent()) { + ers.remove(er); + } + } + } + } + + private class MergeModifierResult { + public final boolean result; + public final ExtractResult er; + + private MergeModifierResult(boolean result, ExtractResult er) { + this.result = result; + this.er = er; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java new file mode 100644 index 000000000..dbf346bd7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java @@ -0,0 +1,160 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseSetExtractor implements IDateTimeExtractor { + + private final ISetExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_SET; + } + + public BaseSetExtractor(ISetExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + tokens.addAll(matchEachUnit(input)); + tokens.addAll(timeEveryday(input, reference)); + tokens.addAll(matchEachDuration(input, reference)); + tokens.addAll(matchEach(this.config.getDateExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getTimeExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getDateTimeExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getDatePeriodExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getTimePeriodExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getDateTimePeriodExtractor(), input, reference)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + public final List matchEachUnit(String text) { + List ret = new ArrayList<>(); + + // handle "daily", "monthly" + Pattern pattern = this.config.getPeriodicRegex(); + Match[] matches = RegExpUtility.getMatches(pattern, text); + + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + // handle "each month" + pattern = this.config.getEachUnitRegex(); + matches = RegExpUtility.getMatches(pattern, text); + + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + public final List matchEachDuration(String text, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getDurationExtractor().extract(text, reference); + + for (ExtractResult er : ers) { + // "each last summer" doesn't make sense + Pattern lastRegex = this.config.getLastRegex(); + if (RegExpUtility.getMatches(lastRegex, er.getText()).length > 0) { + continue; + } + + String beforeStr = text.substring(0, (er.getStart() != null) ? er.getStart() : 0); + Pattern eachPrefixRegex = this.config.getEachPrefixRegex(); + Optional match = Arrays.stream(RegExpUtility.getMatches(eachPrefixRegex, beforeStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } + return ret; + } + + public final List timeEveryday(String text, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getTimeExtractor().extract(text, reference); + for (ExtractResult er : ers) { + String afterStr = text.substring(er.getStart() + er.getLength()); + if (StringUtility.isNullOrEmpty(afterStr) && this.config.getBeforeEachDayRegex() != null) { + String beforeStr = text.substring(0, er.getStart()); + Pattern beforeEachDayRegex = this.config.getBeforeEachDayRegex(); + Optional match = Arrays.stream(RegExpUtility.getMatches(beforeEachDayRegex, beforeStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } else { + Pattern eachDayRegex = this.config.getEachDayRegex(); + Optional match = Arrays.stream(RegExpUtility.getMatches(eachDayRegex, afterStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().length)); + } + } + } + + return ret; + } + + public final List matchEach(IDateTimeExtractor extractor, String input, LocalDateTime reference) { + StringBuilder sb = new StringBuilder(input); + List ret = new ArrayList<>(); + Pattern setEachRegex = this.config.getSetEachRegex(); + Match[] matches = RegExpUtility.getMatches(setEachRegex, input); + for (Match match : matches) { + if (match != null) { + String trimedText = sb.delete(match.index, match.index + match.length).toString(); + List ers = extractor.extract(trimedText, reference); + for (ExtractResult er : ers) { + if (er.getStart() <= match.index && (er.getStart() + er.getLength()) > match.index) { + ret.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.length)); + } + } + } + } + + // handle "Mondays" + Pattern setWeekDayRegex = this.config.getSetWeekDayRegex(); + matches = RegExpUtility.getMatches(setWeekDayRegex, input); + for (Match match : matches) { + if (match != null) { + sb = new StringBuilder(input); + sb.delete(match.index, match.index + match.length); + String trimedText = sb.insert(match.index, match.getGroup("weekday").value).toString(); + + List ers = extractor.extract(trimedText, reference); + for (ExtractResult er : ers) { + if (er.getStart() <= match.index && er.getText().contains(match.getGroup("weekday").value)) { + int len = er.getLength() + 1; + if (match.getGroup(Constants.PrefixGroupName).value != "") { + len += match.getGroup(Constants.PrefixGroupName).value.length(); + } + ret.add(new Token(er.getStart(), er.getStart() + len)); + } + } + } + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java new file mode 100644 index 000000000..3bdcbf876 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java @@ -0,0 +1,149 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class BaseTimeExtractor implements IDateTimeExtractor { + + private final ITimeExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_TIME; + } + + public BaseTimeExtractor(ITimeExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List tokens = new ArrayList<>(); + tokens.addAll(basicRegexMatch(input)); + tokens.addAll(atRegexMatch(input)); + tokens.addAll(beforeAfterRegexMatch(input)); + tokens.addAll(specialCasesRegexMatch(input)); + + List timeErs = Token.mergeAllTokens(tokens, input, getExtractorName()); + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + timeErs = mergeTimeZones(timeErs, config.getTimeZoneExtractor().extract(input, reference), input); + } + + return timeErs; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List mergeTimeZones(List timeErs, List timeZoneErs, String text) { + int erIndex = 0; + for (ExtractResult er : timeErs.toArray(new ExtractResult[0])) { + for (ExtractResult timeZoneEr : timeZoneErs) { + int begin = er.getStart() + er.getLength(); + int end = timeZoneEr.getStart(); + + if (begin < end) { + String gapText = text.substring(begin, end); + + if (StringUtility.isNullOrWhiteSpace(gapText)) { + int newLenght = timeZoneEr.getStart() + timeZoneEr.getLength(); + String newText = text.substring(er.getStart(), newLenght); + Map newData = new HashMap<>(); + newData.put(Constants.SYS_DATETIME_TIMEZONE, timeZoneEr); + + er.setData(newData); + er.setText(newText); + er.setLength(newLenght - er.getStart()); + timeErs.set(erIndex, er); + } + } + } + erIndex++; + } + return timeErs; + } + + public final List basicRegexMatch(String text) { + + List ret = new ArrayList<>(); + + for (Pattern regex : this.config.getTimeRegexList()) { + + Match[] matches = RegExpUtility.getMatches(regex, text); + for (Match match : matches) { + + // @TODO Workaround to avoid incorrect partial-only matches. Remove after time regex reviews across languages. + String lth = match.getGroup("lth").value; + + if ((lth == null || lth.length() == 0) || + (lth.length() != match.length && !(match.length == lth.length() + 1 && match.value.endsWith(" ")))) { + + ret.add(new Token(match.index, match.index + match.length)); + } + } + } + + return ret; + } + + private List atRegexMatch(String text) { + List ret = new ArrayList<>(); + // handle "at 5", "at seven" + Pattern pattern = this.config.getAtRegex(); + Match[] matches = RegExpUtility.getMatches(pattern, text); + if (matches.length > 0) { + for (Match match : matches) { + if (match.index + match.length < text.length() && text.charAt(match.index + match.length) == '%') { + continue; + } + ret.add(new Token(match.index, match.index + match.length)); + } + } + return ret; + } + + private List beforeAfterRegexMatch(String text) { + List ret = new ArrayList<>(); + // only enabled in CalendarMode + if (this.config.getOptions().match(DateTimeOptions.CalendarMode)) { + // handle "before 3", "after three" + Pattern beforeAfterRegex = this.config.getTimeBeforeAfterRegex(); + Match[] matches = RegExpUtility.getMatches(beforeAfterRegex, text); + if (matches.length > 0) { + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + } + return ret; + } + + private List specialCasesRegexMatch(String text) { + List ret = new ArrayList<>(); + // handle "ish" + if (this.config.getIshRegex() != null && RegExpUtility.getMatches(this.config.getIshRegex(), text).length > 0) { + Match[] matches = RegExpUtility.getMatches(this.config.getIshRegex(), text); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java new file mode 100644 index 000000000..d1ee998ec --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java @@ -0,0 +1,275 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseTimePeriodExtractor implements IDateTimeExtractor { + + private final ITimePeriodExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_TIMEPERIOD; + } + + public BaseTimePeriodExtractor(ITimePeriodExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List tokens = new ArrayList<>(); + tokens.addAll(matchSimpleCases(input)); + tokens.addAll(mergeTwoTimePoints(input, reference)); + tokens.addAll(matchTimeOfDay(input)); + + List timePeriodErs = Token.mergeAllTokens(tokens, input, getExtractorName()); + + if (config.getOptions().match(DateTimeOptions.EnablePreview)) { + timePeriodErs = TimeZoneUtility.mergeTimeZones(timePeriodErs, config.getTimeZoneExtractor().extract(input, reference), input); + } + + return timePeriodErs; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + // Cases like "from 3 to 5am" or "between 3:30 and 5" are extracted here + // Note that cases like "from 3 to 5" will not be extracted here because no "am/pm" or "hh:mm" to infer it's a time period + // Also cases like "from 3:30 to 4 people" should not be extracted as a time period + private List matchSimpleCases(String input) { + + List ret = new ArrayList<>(); + + for (Pattern regex : this.config.getSimpleCasesRegex()) { + Match[] matches = RegExpUtility.getMatches(regex, input); + + for (Match match : matches) { + // Cases like "from 10:30 to 11", don't necessarily need "am/pm" + if (!match.getGroup(Constants.MinuteGroupName).value.equals("") || !match.getGroup(Constants.SecondGroupName).value.equals("")) { + // Cases like "from 3:30 to 4" should be supported + // Cases like "from 3:30 to 4 on 1/1/2015" should be supported + // Cases like "from 3:30 to 4 people" is considered not valid + Boolean endWithValidToken = false; + + // "No extra tokens after the time period" + if (match.index + match.length == input.length()) { + endWithValidToken = true; + } else { + String afterStr = input.substring(match.index + match.length); + + // "End with general ending tokens or "TokenBeforeDate" (like "on") + boolean endWithGeneralEndings = Arrays.stream(RegExpUtility.getMatches(this.config.getGeneralEndingRegex(), afterStr)) + .findFirst().isPresent(); + boolean endWithAmPm = !match.getGroup(Constants.RightAmPmGroupName).value.equals(""); + if (endWithGeneralEndings || endWithAmPm || afterStr.trim().startsWith(this.config.getTokenBeforeDate())) { + endWithValidToken = true; + } else if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + endWithValidToken = startsWithTimeZone(afterStr); + } + } + + if (endWithValidToken) { + ret.add(new Token(match.index, match.index + match.length)); + } + } else { + // Is there Constants.PmGroupName or Constants.AmGroupName ? + String pmStr = match.getGroup(Constants.PmGroupName).value; + String amStr = match.getGroup(Constants.AmGroupName).value; + String descStr = match.getGroup(Constants.DescGroupName).value; + + // Check "pm", "am" + if (!StringUtility.isNullOrEmpty(pmStr) || !StringUtility.isNullOrEmpty(amStr) || !StringUtility.isNullOrEmpty(descStr)) { + ret.add(new Token(match.index, match.index + match.length)); + } else { + String afterStr = input.substring(match.index + match.length); + + if ((this.config.getOptions().match(DateTimeOptions.EnablePreview)) && startsWithTimeZone(afterStr)) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + } + } + } + + return ret; + } + + private boolean startsWithTimeZone(String afterText) { + boolean startsWithTimeZone = false; + + List timeZoneErs = config.getTimeZoneExtractor().extract(afterText); + Optional firstTimeZone = timeZoneErs.stream().sorted(Comparator.comparingInt(t -> t.getStart())).findFirst(); + + if (firstTimeZone.isPresent()) { + String beforeText = afterText.substring(0, firstTimeZone.get().getStart()); + + if (StringUtility.isNullOrWhiteSpace(beforeText)) { + startsWithTimeZone = true; + } + } + + return startsWithTimeZone; + } + + private List mergeTwoTimePoints(String input, LocalDateTime reference) { + + List ret = new ArrayList<>(); + List ers = this.config.getSingleTimeExtractor().extract(input, reference); + + // Handling ending number as a time point. + List numErs = this.config.getIntegerExtractor().extract(input); + + // Check if it is an ending number + if (numErs.size() > 0) { + List timeNumbers = new ArrayList<>(); + + // check if it is a ending number + boolean endingNumber = false; + ExtractResult num = numErs.get(numErs.size() - 1); + if (num.getStart() + num.getLength() == input.length()) { + endingNumber = true; + } else { + String afterStr = input.substring(num.getStart() + num.getLength()); + Pattern generalEndingRegex = this.config.getGeneralEndingRegex(); + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(generalEndingRegex, input)).findFirst(); + if (endingMatch.isPresent()) { + endingNumber = true; + } + } + if (endingNumber) { + timeNumbers.add(num); + } + + int i = 0; + int j = 0; + + while (i < numErs.size()) { + // find subsequent time point + int numEndPoint = numErs.get(i).getStart() + numErs.get(i).getLength(); + while (j < ers.size() && ers.get(j).getStart() <= numEndPoint) { + j++; + } + + if (j >= ers.size()) { + break; + } + + // check connector string + String midStr = input.substring(numEndPoint, ers.get(j).getStart()); + Pattern tillRegex = this.config.getTillRegex(); + if (RegexExtension.isExactMatch(tillRegex, midStr, true) || config.hasConnectorToken(midStr.trim())) { + timeNumbers.add(numErs.get(i)); + } + + i++; + } + + // check overlap + for (ExtractResult timeNum : timeNumbers) { + boolean overlap = false; + for (ExtractResult er : ers) { + if (er.getStart() <= timeNum.getStart() && er.getStart() + er.getLength() >= timeNum.getStart()) { + overlap = true; + } + } + + if (!overlap) { + ers.add(timeNum); + } + } + + ers.sort((x, y) -> x.getStart() - y.getStart()); + } + + int idx = 0; + while (idx < ers.size() - 1) { + int middleBegin = ers.get(idx).getStart() + ers.get(idx).getLength(); + int middleEnd = ers.get(idx + 1).getStart(); + + if (middleEnd - middleBegin <= 0) { + idx++; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(java.util.Locale.ROOT); + Pattern tillRegex = this.config.getTillRegex(); + + // Handle "{TimePoint} to {TimePoint}" + if (RegexExtension.isExactMatch(tillRegex, middleStr, true)) { + int periodBegin = ers.get(idx).getStart(); + int periodEnd = ers.get(idx + 1).getStart() + ers.get(idx + 1).getLength(); + + // Handle "from" + String beforeStr = StringUtility.trimEnd(input.substring(0, periodBegin)).toLowerCase(); + ResultIndex fromIndex = this.config.getFromTokenIndex(beforeStr); + ResultIndex betweenIndex = this.config.getBetweenTokenIndex(beforeStr); + if (fromIndex.getResult()) { + // Handle "from" + periodBegin = fromIndex.getIndex(); + } else if (betweenIndex.getResult()) { + // Handle "between" + periodBegin = betweenIndex.getIndex(); + } + + ret.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + + // Handle "between {TimePoint} and {TimePoint}" + if (this.config.hasConnectorToken(middleStr)) { + int periodBegin = ers.get(idx).getStart(); + int periodEnd = ers.get(idx + 1).getStart() + ers.get(idx + 1).getLength(); + + // Handle "between" + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(java.util.Locale.ROOT); + ResultIndex betweenIndex = this.config.getBetweenTokenIndex(beforeStr); + if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + ret.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + } + + idx++; + } + + return ret; + } + + private List matchTimeOfDay(String input) { + + List ret = new ArrayList<>(); + Pattern timeOfDayRegex = this.config.getTimeOfDayRegex(); + Match[] matches = RegExpUtility.getMatches(timeOfDayRegex, input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java new file mode 100644 index 000000000..e1401d428 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java @@ -0,0 +1,133 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.MatchingUtil; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.matcher.MatchResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class BaseTimeZoneExtractor implements IDateTimeZoneExtractor { + + private final ITimeZoneExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_TIMEZONE; + } + + public BaseTimeZoneExtractor(ITimeZoneExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + String normalizedText = QueryProcessor.removeDiacritics(input); + List tokens = new ArrayList<>(); + tokens.addAll(matchTimeZones(normalizedText)); + tokens.addAll(matchLocationTimes(normalizedText, tokens)); + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + @Override + public List removeAmbiguousTimezone(List extractResults) { + return extractResults.stream().filter(o -> !config.getAmbiguousTimezoneList().contains(o.getText().toLowerCase())).collect(Collectors.toList()); + } + + private List matchLocationTimes(String text, List tokens) { + List ret = new ArrayList<>(); + + if (config.getLocationTimeSuffixRegex() == null) { + return ret; + } + + Match[] timeMatch = RegExpUtility.getMatches(config.getLocationTimeSuffixRegex(), text); + + // Before calling a Find() in location matcher, check if all the matched suffixes by + // LocationTimeSuffixRegex are already inside tokens extracted by TimeZone matcher. + // If so, don't call the Find() as they have been extracted by TimeZone matcher, otherwise, call it. + + boolean isAllSuffixInsideTokens = true; + + for (Match match : timeMatch) { + boolean isInside = false; + for (Token token : tokens) { + if (token.getStart() <= match.index && token.getEnd() >= match.index + match.length) { + isInside = true; + break; + } + } + + if (!isInside) { + isAllSuffixInsideTokens = false; + } + + if (!isAllSuffixInsideTokens) { + break; + } + } + + if (timeMatch.length != 0 && !isAllSuffixInsideTokens) { + int lastMatchIndex = timeMatch[timeMatch.length - 1].index; + Iterable> matches = config.getLocationMatcher().find(text.substring(0, lastMatchIndex).toLowerCase()); + List> locationMatches = MatchingUtil.removeSubMatches(matches); + + int i = 0; + for (Match match : timeMatch) { + boolean hasCityBefore = false; + + while (i < locationMatches.size() && locationMatches.get(i).getEnd() <= match.index) { + hasCityBefore = true; + i++; + + if (i == locationMatches.size()) { + break; + } + } + + if (hasCityBefore && locationMatches.get(i - 1).getEnd() == match.index) { + ret.add(new Token(locationMatches.get(i - 1).getStart(), match.index + match.length)); + } + + if (i == locationMatches.size()) { + break; + } + } + } + + return ret; + } + + private List matchTimeZones(String text) { + List ret = new ArrayList<>(); + + // Direct UTC matches + Match[] directUtcMatches = RegExpUtility.getMatches(config.getDirectUtcRegex(), text.toLowerCase()); + if (directUtcMatches.length > 0) { + for (Match match : directUtcMatches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + + Iterable> matches = config.getTimeZoneMatcher().find(text.toLowerCase()); + for (MatchResult match : matches) { + ret.add(new Token(match.getStart(), match.getStart() + match.getLength())); + } + + return ret; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java new file mode 100644 index 000000000..78d19f42e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.utilities.Match; + +public interface IDateExtractor extends IDateTimeExtractor { + int getYearFromText(Match match); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java new file mode 100644 index 000000000..f21f49c41 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; + +import java.time.LocalDateTime; +import java.util.List; + +public interface IDateTimeExtractor extends IExtractor { + String getExtractorName(); + + List extract(String input, LocalDateTime reference); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java new file mode 100644 index 000000000..4d7ab6d5c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.time.LocalDateTime; +import java.util.List; + +public interface IDateTimeListExtractor { + String getExtractorName(); + + List extract(List extractResults, String text, LocalDateTime reference); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java new file mode 100644 index 000000000..8b9b1a151 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.util.List; + +public interface IDateTimeZoneExtractor extends IDateTimeExtractor { + List removeAmbiguousTimezone(List extractResults); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java new file mode 100644 index 000000000..4498324bb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java @@ -0,0 +1,62 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface IDateExtractorConfiguration extends IOptionsConfiguration { + Iterable getDateRegexList(); + + Iterable getImplicitDateList(); + + Pattern getOfMonth(); + + Pattern getMonthEnd(); + + Pattern getWeekDayEnd(); + + Pattern getDateUnitRegex(); + + Pattern getForTheRegex(); + + Pattern getWeekDayAndDayOfMonthRegex(); + + Pattern getRelativeMonthRegex(); + + Pattern getStrictRelativeRegex(); + + Pattern getWeekDayRegex(); + + Pattern getPrefixArticleRegex(); + + Pattern getYearSuffix(); + + Pattern getMoreThanRegex(); + + Pattern getLessThanRegex(); + + Pattern getInConnectorRegex(); + + Pattern getRangeUnitRegex(); + + Pattern getRangeConnectorSymbolRegex(); + + IExtractor getIntegerExtractor(); + + IExtractor getOrdinalExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + ImmutableMap getDayOfWeek(); + + ImmutableMap getMonthOfYear(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..6c7996691 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java @@ -0,0 +1,79 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface IDatePeriodExtractorConfiguration { + Iterable getSimpleCasesRegexes(); + + Pattern getIllegalYearRegex(); + + Pattern getYearRegex(); + + Pattern getTillRegex(); + + Pattern getDateUnitRegex(); + + Pattern getTimeUnitRegex(); + + Pattern getFollowedDateUnit(); + + Pattern getNumberCombinedWithDateUnit(); + + Pattern getPastRegex(); + + Pattern getFutureRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getWeekOfRegex(); + + Pattern getMonthOfRegex(); + + Pattern getRangeUnitRegex(); + + Pattern getInConnectorRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getYearPeriodRegex(); + + Pattern getRelativeDecadeRegex(); + + Pattern getComplexDatePeriodRegex(); + + Pattern getReferenceDatePeriodRegex(); + + Pattern getAgoRegex(); + + Pattern getLaterRegex(); + + Pattern getLessThanRegex(); + + Pattern getMoreThanRegex(); + + Pattern getCenturySuffixRegex(); + + Pattern getNowRegex(); + + IDateTimeExtractor getDatePointExtractor(); + + IExtractor getCardinalExtractor(); + + IExtractor getOrdinalExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IParser getNumberParser(); + + ResultIndex getFromTokenIndex(String text); + + boolean hasConnectorToken(String text); + + ResultIndex getBetweenTokenIndex(String text); + + String[] getDurationDateRestrictions(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..8bddc5321 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java @@ -0,0 +1,24 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface IDateTimeAltExtractorConfiguration { + IDateExtractor getDateExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + Iterable getRelativePrefixList(); + + Iterable getAmPmRegexList(); + + Pattern getOrRegex(); + + Pattern getThisPrefixRegex(); + + Pattern getDayRegex(); + + Pattern getRangePrefixRegex(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..e26f83509 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface IDateTimeExtractorConfiguration extends IOptionsConfiguration { + Pattern getNowRegex(); + + Pattern getSuffixRegex(); + + Pattern getTimeOfTodayAfterRegex(); + + Pattern getSimpleTimeOfTodayAfterRegex(); + + Pattern getTimeOfTodayBeforeRegex(); + + Pattern getSimpleTimeOfTodayBeforeRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getSpecificEndOfRegex(); + + Pattern getUnspecificEndOfRegex(); + + Pattern getUnitRegex(); + + Pattern getNumberAsTimeRegex(); + + Pattern getDateNumberConnectorRegex(); + + Pattern getSuffixAfterRegex(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getDatePointExtractor(); + + IDateTimeExtractor getTimePointExtractor(); + + IExtractor getIntegerExtractor(); + + boolean isConnector(String text); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..b0cc61ad7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,81 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface IDateTimePeriodExtractorConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + Iterable getSimpleCasesRegex(); + + Pattern getPrepositionRegex(); + + Pattern getTillRegex(); + + Pattern getSpecificTimeOfDayRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getFollowedUnit(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getTimeUnitRegex(); + + Pattern getPastPrefixRegex(); + + Pattern getNextPrefixRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getWeekDayRegex(); + + Pattern getPeriodTimeOfDayWithDateRegex(); + + Pattern getRelativeTimeUnitRegex(); + + Pattern getRestOfDateTimeRegex(); + + Pattern getGeneralEndingRegex(); + + Pattern getMiddlePauseRegex(); + + Pattern getAmDescRegex(); + + Pattern getPmDescRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getDateUnitRegex(); + + Pattern getPrefixDayRegex(); + + Pattern getSuffixRegex(); + + Pattern getBeforeRegex(); + + Pattern getAfterRegex(); + + IExtractor getCardinalExtractor(); + + IDateTimeExtractor getSingleDateExtractor(); + + IDateTimeExtractor getSingleTimeExtractor(); + + IDateTimeExtractor getSingleDateTimeExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getTimeZoneExtractor(); + + ResultIndex getFromTokenIndex(String text); + + boolean hasConnectorToken(String text); + + ResultIndex getBetweenTokenIndex(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java new file mode 100644 index 000000000..b68aee159 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java @@ -0,0 +1,45 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; + +import java.util.regex.Pattern; + +public interface IDurationExtractorConfiguration extends IOptionsConfiguration { + Pattern getFollowedUnit(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getAnUnitRegex(); + + Pattern getDuringRegex(); + + Pattern getAllRegex(); + + Pattern getHalfRegex(); + + Pattern getSuffixAndRegex(); + + Pattern getConjunctionRegex(); + + Pattern getInexactNumberRegex(); + + Pattern getInexactNumberUnitRegex(); + + Pattern getRelativeDurationUnitRegex(); + + Pattern getDurationUnitRegex(); + + Pattern getDurationConnectorRegex(); + + Pattern getLessThanRegex(); + + Pattern getMoreThanRegex(); + + IExtractor getCardinalExtractor(); + + ImmutableMap getUnitMap(); + + ImmutableMap getUnitValueMap(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java new file mode 100644 index 000000000..f071ed03f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import java.util.regex.Pattern; + +public interface IHolidayExtractorConfiguration { + Iterable getHolidayRegexes(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java new file mode 100644 index 000000000..ad1c14367 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java @@ -0,0 +1,68 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public interface IMergedExtractorConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDateTimePeriodExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getSetExtractor(); + + IDateTimeExtractor getHolidayExtractor(); + + IDateTimeZoneExtractor getTimeZoneExtractor(); + + IDateTimeListExtractor getDateTimeAltExtractor(); + + IExtractor getIntegerExtractor(); + + Iterable getFilterWordRegexList(); + + Pattern getAfterRegex(); + + Pattern getBeforeRegex(); + + Pattern getSinceRegex(); + + Pattern getAroundRegex(); + + Pattern getFromToRegex(); + + Pattern getSingleAmbiguousMonthRegex(); + + Pattern getAmbiguousRangeModifierPrefix(); + + Pattern getPotentialAmbiguousRangeRegex(); + + Pattern getPrepositionSuffixRegex(); + + Pattern getNumberEndingPattern(); + + Pattern getSuffixAfterRegex(); + + Pattern getUnspecificDatePeriodRegex(); + + StringMatcher getSuperfluousWordMatcher(); + + Iterable> getAmbiguityFiltersDict(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java new file mode 100644 index 000000000..35620d82a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface ISetExtractorConfiguration extends IOptionsConfiguration { + Pattern getLastRegex(); + + Pattern getEachDayRegex(); + + Pattern getSetEachRegex(); + + Pattern getPeriodicRegex(); + + Pattern getEachUnitRegex(); + + Pattern getEachPrefixRegex(); + + Pattern getSetWeekDayRegex(); + + Pattern getBeforeEachDayRegex(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDateTimePeriodExtractor(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java new file mode 100644 index 000000000..9c025d24e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface ITimeExtractorConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getTimeZoneExtractor(); + + Iterable getTimeRegexList(); + + Pattern getAtRegex(); + + Pattern getIshRegex(); + + Pattern getTimeBeforeAfterRegex(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..9f0a0cbf9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface ITimePeriodExtractorConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + IExtractor getIntegerExtractor(); + + Iterable getSimpleCasesRegex(); + + Pattern getTillRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getGeneralEndingRegex(); + + IDateTimeExtractor getSingleTimeExtractor(); + + ResultIndex getFromTokenIndex(String text); + + boolean hasConnectorToken(String text); + + ResultIndex getBetweenTokenIndex(String text); + + IDateTimeExtractor getTimeZoneExtractor(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..ce877754c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.List; +import java.util.regex.Pattern; + +public interface ITimeZoneExtractorConfiguration extends IOptionsConfiguration { + Pattern getDirectUtcRegex(); + + Pattern getLocationTimeSuffixRegex(); + + StringMatcher getLocationMatcher(); + + StringMatcher getTimeZoneMatcher(); + + List getAmbiguousTimezoneList(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java new file mode 100644 index 000000000..42e59194e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java @@ -0,0 +1,23 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.matcher.MatchResult; + +import java.util.List; + +public class ProcessedSuperfluousWords { + private String text; + private Iterable> superfluousWordMatches; + + public ProcessedSuperfluousWords(String text, Iterable> superfluousWordMatches) { + this.text = text; + this.superfluousWordMatches = superfluousWordMatches; + } + + public String getText() { + return text; + } + + public Iterable> getSuperfluousWordMatches() { + return superfluousWordMatches; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java new file mode 100644 index 000000000..b9d90d992 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +public class ResultIndex { + private boolean result; + private int index; + + public ResultIndex(boolean result, int index) { + this.result = result; + this.index = index; + } + + public boolean getResult() { + return result; + } + + public int getIndex() { + return index; + } + + public void setResult(boolean result) { + this.result = result; + } + + public void setIndex(int index) { + this.index = index; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java new file mode 100644 index 000000000..eaff0b5b3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +public class ResultTimex { + private boolean result; + private String timex; + + public ResultTimex(boolean result, String timex) { + this.result = result; + this.timex = timex; + } + + public boolean getResult() { + return result; + } + + public String getTimex() { + return timex; + } + + public void setResult(boolean result) { + this.result = result; + } + + public void setTimex(String timex) { + this.timex = timex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java new file mode 100644 index 000000000..a90a1f5e9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java @@ -0,0 +1,245 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class FrenchDateExtractorConfiguration extends BaseOptionsConfiguration implements IDateExtractorConfiguration { + + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthNumRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearRegex); + public static final Pattern WeekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayRegex); + public static final Pattern SingleWeekDayRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SingleAmbiguousMonthRegex); + public static final Pattern OnRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OnRegex); + public static final Pattern RelaxedOnRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelaxedOnRegex); + public static final Pattern ThisRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ThisRegex); + public static final Pattern LastDateRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LastDateRegex); + public static final Pattern NextDateRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextDateRegex); + public static final Pattern StrictWeekDay = RegExpUtility.getSafeRegExp(FrenchDateTime.StrictWeekDay); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + public static final Pattern SpecialDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecialDayRegex); + public static final Pattern WeekDayOfMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayOfMonthRegex); + public static final Pattern RelativeWeekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeWeekDayRegex); + public static final Pattern SpecialDate = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecialDate); + public static final Pattern SpecialDayWithNumRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SpecialDayWithNumRegex); + public static final Pattern ForTheRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ForTheRegex); + public static final Pattern WeekDayAndDayOfMonthRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WeekDayAndDayOfMonthRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeMonthRegex); + public static final Pattern StrictRelativeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.StrictRelativeRegex); + public static final Pattern PrefixArticleRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PrefixArticleRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InConnectorRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeUnitRegex); + public static final Pattern RangeConnectorSymbolRegex = RegExpUtility + .getSafeRegExp(BaseDateTime.RangeConnectorSymbolRegex); + + public static final List DateRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor1)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor2)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor3)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor5) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor4)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor4) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor5)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor7) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor6)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor6) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor7)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor8)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor9)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractorA)); + } + }; + + public static final List ImplicitDateList = new ArrayList() { + { + add(OnRegex); + add(RelaxedOnRegex); + add(SpecialDayRegex); + add(ThisRegex); + add(LastDateRegex); + add(NextDateRegex); + add(StrictWeekDay); + add(WeekDayOfMonthRegex); + add(SpecialDate); + } + }; + + public static final Pattern OfMonth = RegExpUtility.getSafeRegExp(FrenchDateTime.OfMonth); + public static final Pattern MonthEnd = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthEnd); + public static final Pattern WeekDayEnd = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayEnd); + public static final Pattern YearSuffix = RegExpUtility.getSafeRegExp(FrenchDateTime.YearSuffix); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MoreThanRegex); + + public static final ImmutableMap DayOfWeek = FrenchDateTime.DayOfWeek; + public static final ImmutableMap MonthOfYear = FrenchDateTime.MonthOfYear; + + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final List implicitDateList; + + public FrenchDateExtractorConfiguration(final IOptionsConfiguration config) { + super(config.getOptions()); + integerExtractor = new IntegerExtractor(); + ordinalExtractor = new OrdinalExtractor(); + numberParser = new BaseNumberParser(new FrenchNumberParserConfiguration()); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + + implicitDateList = new ArrayList<>(ImplicitDateList); + } + + @Override + public Iterable getDateRegexList() { + return DateRegexList; + } + + @Override + public Iterable getImplicitDateList() { + return implicitDateList; + } + + @Override + public Pattern getOfMonth() { + return OfMonth; + } + + @Override + public Pattern getMonthEnd() { + return MonthEnd; + } + + @Override + public Pattern getWeekDayEnd() { + return WeekDayEnd; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getForTheRegex() { + return ForTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return WeekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return RelativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return StrictRelativeRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return WeekDayRegex; + } + + @Override + public Pattern getPrefixArticleRegex() { + return PrefixArticleRegex; + } + + @Override + public Pattern getYearSuffix() { + return YearSuffix; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getRangeConnectorSymbolRegex() { + return RangeConnectorSymbolRegex; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfWeek() { + return DayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return MonthOfYear; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..0f87a53c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java @@ -0,0 +1,328 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchDatePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDatePeriodExtractorConfiguration { + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TillRegex); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeConnectorRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthNumRegex); + public static final Pattern IllegalYearRegex = RegExpUtility.getSafeRegExp(BaseDateTime.IllegalYearRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeMonthRegex); + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthRegex); + public static final Pattern MonthSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthSuffixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + public static final Pattern PastRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PastSuffixRegex); + public static final Pattern FutureRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextSuffixRegex); + public static final Pattern FutureSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FutureSuffixRegex); + + // composite regexes + public static final Pattern SimpleCasesRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SimpleCasesRegex); + public static final Pattern MonthFrontSimpleCasesRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.MonthFrontSimpleCasesRegex); + public static final Pattern MonthFrontBetweenRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.MonthFrontBetweenRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BetweenRegex); + public static final Pattern OneWordPeriodRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OneWordPeriodRegex); + public static final Pattern MonthWithYearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthWithYear); + public static final Pattern MonthNumWithYearRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.MonthNumWithYear); + public static final Pattern WeekOfMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekOfMonthRegex); + public static final Pattern WeekOfYearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekOfYearRegex); + public static final Pattern FollowedDateUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.FollowedDateUnit); + public static final Pattern NumberCombinedWithDateUnit = RegExpUtility + .getSafeRegExp(FrenchDateTime.NumberCombinedWithDateUnit); + public static final Pattern QuarterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.QuarterRegex); + public static final Pattern QuarterRegexYearFront = RegExpUtility + .getSafeRegExp(FrenchDateTime.QuarterRegexYearFront); + public static final Pattern AllHalfYearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AllHalfYearRegex); + public static final Pattern SeasonRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SeasonRegex); + public static final Pattern WhichWeekRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WhichWeekRegex); + public static final Pattern WeekOfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekOfRegex); + public static final Pattern MonthOfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthOfRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeUnitRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WithinNextPrefixRegex); + public static final Pattern LaterEarlyPeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.LaterEarlyPeriodRegex); + public static final Pattern RestOfDateRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RestOfDateRegex); + public static final Pattern WeekWithWeekDayRangeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WeekWithWeekDayRangeRegex); + public static final Pattern YearPlusNumberRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearPlusNumberRegex); + public static final Pattern DecadeWithCenturyRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.DecadeWithCenturyRegex); + public static final Pattern YearPeriodRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearPeriodRegex); + public static final Pattern ComplexDatePeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.ComplexDatePeriodRegex); + public static final Pattern RelativeDecadeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeDecadeRegex); + public static final Pattern ReferenceDatePeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.ReferenceDatePeriodRegex); + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LaterRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MoreThanRegex); + public static final Pattern CenturySuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.CenturySuffixRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NowRegex); + + public static final Iterable SimpleCasesRegexes = new ArrayList() { + { + add(SimpleCasesRegex); + add(BetweenRegex); + add(OneWordPeriodRegex); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.MonthWithYear)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.MonthNumWithYear)); + add(YearRegex); + add(YearPeriodRegex); + add(WeekOfYearRegex); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayOfMonthRegex)); + add(MonthFrontBetweenRegex); + add(MonthFrontSimpleCasesRegex); + add(QuarterRegex); + add(QuarterRegexYearFront); + add(SeasonRegex); + add(LaterEarlyPeriodRegex); + add(YearPlusNumberRegex); + add(DecadeWithCenturyRegex); + add(RelativeDecadeRegex); + } + }; + + private static final Pattern fromRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromRegex); + private static final Pattern betweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BetweenRegex); + + private final IDateTimeExtractor datePointExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IParser numberParser; + private final String[] durationDateRestrictions; + + public FrenchDatePeriodExtractorConfiguration(final IOptionsConfiguration config) { + super(config.getOptions()); + + datePointExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + cardinalExtractor = CardinalExtractor.getInstance(); + ordinalExtractor = new OrdinalExtractor(); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + numberParser = new BaseNumberParser(new FrenchNumberParserConfiguration()); + + durationDateRestrictions = FrenchDateTime.DurationDateRestrictions.toArray(new String[0]); + } + + @Override + public Iterable getSimpleCasesRegexes() { + return SimpleCasesRegexes; + } + + @Override + public Pattern getIllegalYearRegex() { + return IllegalYearRegex; + } + + @Override + public Pattern getYearRegex() { + return YearRegex; + } + + @Override + public Pattern getTillRegex() { + return TillRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getFollowedDateUnit() { + return FollowedDateUnit; + } + + @Override + public Pattern getNumberCombinedWithDateUnit() { + return NumberCombinedWithDateUnit; + } + + @Override + public Pattern getPastRegex() { + return PastRegex; + } + + @Override + public Pattern getFutureRegex() { + return FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FutureSuffixRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return WeekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return MonthOfRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return YearPeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return RelativeDecadeRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return ReferenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return CenturySuffixRegex; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public String[] getDurationDateRestrictions() { + return durationDateRestrictions; + } + + @Override + public ResultIndex getFromTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = fromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = betweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(final String text) { + final Optional match = Arrays.stream(RegExpUtility.getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorAndRegex), text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return ComplexDatePeriodRegex; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..a07c59c04 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java @@ -0,0 +1,86 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchDateTimeAltExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeAltExtractorConfiguration { + + public static final Pattern ThisPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ThisPrefixRegex); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextPrefixRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex); + public static final Pattern RangePrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangePrefixRegex); + public static final Iterable RelativePrefixList = new ArrayList() { + { + add(ThisPrefixRegex); + add(PreviousPrefixRegex); + add(NextPrefixRegex); + } + }; + public static final Iterable AmPmRegexList = new ArrayList() { + { + add(AmRegex); + add(PmRegex); + } + }; + private static final Pattern OrRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OrRegex); + private static final Pattern DayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DayRegex); + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor datePeriodExtractor; + + public FrenchDateTimeAltExtractorConfiguration(final IOptionsConfiguration config) { + super(config.getOptions()); + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public Iterable getRelativePrefixList() { + return RelativePrefixList; + } + + @Override + public Iterable getAmPmRegexList() { + return AmPmRegexList; + } + + @Override + public Pattern getOrRegex() { + return OrRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return ThisPrefixRegex; + } + + @Override + public Pattern getDayRegex() { + return DayRegex; + } + + @Override + public Pattern getRangePrefixRegex() { + return RangePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..da47f1fbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java @@ -0,0 +1,171 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.util.Arrays; +import java.util.regex.Pattern; + +public class FrenchDateTimeExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeExtractorConfiguration { + + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PrepositionRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NowRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixRegex); + + //TODO: modify it according to the corresponding English regex + + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeOfDayRegex); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeOfTodayAfterRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeOfTodayAfterRegex); + public static final Pattern TimeOfTodayBeforeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeOfTodayBeforeRegex); + public static final Pattern SimpleTimeOfTodayAfterRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SimpleTimeOfTodayAfterRegex); + public static final Pattern SimpleTimeOfTodayBeforeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SimpleTimeOfTodayBeforeRegex); + public static final Pattern SpecificEndOfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecificEndOfRegex); + public static final Pattern UnspecificEndOfRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.UnspecificEndOfRegex); + + //TODO: add this for french + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + public static final Pattern ConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorRegex); + public static final Pattern NumberAsTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NumberAsTimeRegex); + public static final Pattern DateNumberConnectorRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.DateNumberConnectorRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixAfterRegex); + private final IExtractor integerExtractor; + private final IDateExtractor datePointExtractor; + private final IDateTimeExtractor timePointExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchDateTimeExtractorConfiguration(final DateTimeOptions options) { + + super(options); + + integerExtractor = IntegerExtractor.getInstance(); + datePointExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + timePointExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(options)); + + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + } + + public FrenchDateTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IDateTimeExtractor getTimePointExtractor() { + return timePointExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getTimeOfTodayAfterRegex() { + return TimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return SimpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getTimeOfTodayBeforeRegex() { + return TimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return SimpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return SpecificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return UnspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return UnitRegex; + } + + @Override + public Pattern getNumberAsTimeRegex() { + return NumberAsTimeRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return DateNumberConnectorRegex; + } + + @Override + public Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public boolean isConnector(String text) { + + text = text.trim(); + + final boolean isPreposition = Arrays.stream(RegExpUtility.getMatches(PrepositionRegex, text)).findFirst().isPresent(); + final boolean isConnector = Arrays.stream(RegExpUtility.getMatches(ConnectorRegex, text)).findFirst().isPresent(); + return (StringUtility.isNullOrEmpty(text) || isPreposition || isConnector); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..bbd3c23e1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,283 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchDateTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodExtractorConfiguration { + + public static final Pattern weekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayRegex); + public static final Pattern NumberCombinedWithUnit = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeNumberCombinedWithUnit); + public static final Pattern RestOfDateTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RestOfDateTimeRegex); + public static final Pattern PeriodTimeOfDayWithDateRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.PeriodTimeOfDayWithDateRegex); + public static final Pattern RelativeTimeUnitRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.RelativeTimeUnitRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.GeneralEndingRegex); + public static final Pattern MiddlePauseRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MiddlePauseRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmDescRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WithinNextPrefixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + public static final Pattern PrefixDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PrefixDayRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AfterRegex); + public static final Pattern FromRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromRegex2); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeConnectorRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BetweenRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeOfDayRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeFollowedUnit); + public static final Iterable SimpleCases = new ArrayList() { + { + add(FrenchTimePeriodExtractorConfiguration.PureNumFromTo); + add(FrenchTimePeriodExtractorConfiguration.PureNumBetweenAnd); + add(FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex); + } + }; + private final String tokenBeforeDate; + private final IExtractor cardinalExtractor; + private final IDateTimeExtractor singleDateExtractor; + private final IDateTimeExtractor singleTimeExtractor; + private final IDateTimeExtractor singleDateTimeExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + public FrenchDateTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchDateTimePeriodExtractorConfiguration(final DateTimeOptions options) { + + super(options); + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + + cardinalExtractor = CardinalExtractor.getInstance(); + + singleDateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + singleTimeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + singleDateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(options)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateExtractor() { + return singleDateExtractor; + } + + @Override + public IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateTimeExtractor() { + return singleDateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + @Override + public Iterable getSimpleCasesRegex() { + return SimpleCases; + } + + @Override + public Pattern getPrepositionRegex() { + return FrenchDateTimeExtractorConfiguration.PrepositionRegex; + } + + @Override + public Pattern getTillRegex() { + return FrenchTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return FrenchDateTimeExtractorConfiguration.TimeOfDayRegex; + } + + @Override + public Pattern getFollowedUnit() { + return TimeFollowedUnit; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return FrenchDatePeriodExtractorConfiguration.PastRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return FrenchDatePeriodExtractorConfiguration.FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FrenchDatePeriodExtractorConfiguration.FutureSuffixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return PrefixDayRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return PeriodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return RelativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return RestOfDateTimeRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public Pattern getMiddlePauseRegex() { + return MiddlePauseRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getBeforeRegex() { + return BeforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return AfterRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + } + + @Override + public ResultIndex getFromTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(final String text) { + final Optional match = Arrays.stream(RegExpUtility.getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorAndRegex), text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java new file mode 100644 index 000000000..400d02329 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java @@ -0,0 +1,139 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDurationExtractorConfiguration extends BaseOptionsConfiguration implements IDurationExtractorConfiguration { + + // TODO: Investigate if required + // public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.UnitRegex); + public static final Pattern SuffixAndRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixAndRegex); + public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.DurationFollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.NumberCombinedWithDurationUnit); + public static final Pattern AnUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AnUnitRegex); + public static final Pattern DuringRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DuringRegex); + public static final Pattern AllRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AllRegex); + public static final Pattern HalfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.HalfRegex); + public static final Pattern ConjunctionRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ConjunctionRegex); + public static final Pattern InexactNumberRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InexactNumberRegex); + public static final Pattern InexactNumberUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InexactNumberUnitRegex); + public static final Pattern RelativeDurationUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeDurationUnitRegex); + public static final Pattern DurationUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DurationUnitRegex); + public static final Pattern DurationConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DurationConnectorRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MoreThanRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanRegex); + + private final IExtractor cardinalExtractor; + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + + public FrenchDurationExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchDurationExtractorConfiguration(final DateTimeOptions options) { + + super(options); + + cardinalExtractor = CardinalExtractor.getInstance(); + unitMap = FrenchDateTime.UnitMap; + unitValueMap = FrenchDateTime.UnitValueMap; + } + + @Override + public Pattern getFollowedUnit() { + return FollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return AnUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return DuringRegex; + } + + @Override + public Pattern getAllRegex() { + return AllRegex; + } + + @Override + public Pattern getHalfRegex() { + return HalfRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return SuffixAndRegex; + } + + @Override + public Pattern getConjunctionRegex() { + return ConjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return InexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return InexactNumberUnitRegex; + } + + @Override + public Pattern getRelativeDurationUnitRegex() { + return RelativeDurationUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return DurationUnitRegex; + } + + @Override + public Pattern getDurationConnectorRegex() { + return DurationConnectorRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java new file mode 100644 index 000000000..fc3cef61e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchHolidayExtractorConfiguration extends BaseOptionsConfiguration implements IHolidayExtractorConfiguration { + + public static final Pattern H1 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex1); + + public static final Pattern H2 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex2); + + public static final Pattern H3 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex3); + + public static final Pattern H4 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex4); + + public static final Iterable HolidayRegexList = new ArrayList() { + { + add(H1); + add(H2); + add(H3); + add(H4); + } + }; + + public FrenchHolidayExtractorConfiguration() { + super(DateTimeOptions.None); + } + + @Override + public Iterable getHolidayRegexes() { + return HolidayRegexList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java new file mode 100644 index 000000000..f49ec1307 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java @@ -0,0 +1,194 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeAltExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseHolidayExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseSetExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +public class FrenchMergedExtractorConfiguration extends BaseOptionsConfiguration implements IMergedExtractorConfiguration { + + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AfterRegex); + public static final Pattern SinceRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SinceRegex); + public static final Pattern AroundRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AroundRegex); + public static final Pattern FromToRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromToRegex); + public static final Pattern SingleAmbiguousMonthRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SingleAmbiguousMonthRegex); + public static final Pattern PrepositionSuffixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.PrepositionSuffixRegex); + public static final Pattern AmbiguousRangeModifierPrefix = RegExpUtility + .getSafeRegExp(FrenchDateTime.AmbiguousRangeModifierPrefix); + public static final Pattern NumberEndingPattern = RegExpUtility.getSafeRegExp(FrenchDateTime.NumberEndingPattern); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixAfterRegex); + public static final Pattern UnspecificDatePeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.UnspecificDatePeriodRegex); + public static final StringMatcher SuperfluousWordMatcher = new StringMatcher(); + public final Iterable> ambiguityFiltersDict = FrenchDateTime.AmbiguityFiltersDict.entrySet().stream().map(pair -> { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + return new Pair(key, val); + }).collect(Collectors.toList()); + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor setExtractor; + private final IDateTimeExtractor holidayExtractor; + private final IDateTimeZoneExtractor timeZoneExtractor; + private final IDateTimeListExtractor dateTimeAltExtractor; + private final IExtractor integerExtractor; + + public FrenchMergedExtractorConfiguration(final DateTimeOptions options) { + super(options); + + setExtractor = new BaseSetExtractor(new FrenchSetExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + holidayExtractor = new BaseHolidayExtractor(new FrenchHolidayExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + dateTimeAltExtractor = new BaseDateTimeAltExtractor(new FrenchDateTimeAltExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor( + new FrenchDateTimePeriodExtractorConfiguration(options)); + integerExtractor = new IntegerExtractor(); + } + + public final StringMatcher getSuperfluousWordMatcher() { + return SuperfluousWordMatcher; + } + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeExtractor getSetExtractor() { + return setExtractor; + } + + public final IDateTimeExtractor getHolidayExtractor() { + return holidayExtractor; + } + + public final IDateTimeZoneExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + public final IDateTimeListExtractor getDateTimeAltExtractor() { + return dateTimeAltExtractor; + } + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public final Iterable> getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + @Override + public Iterable getFilterWordRegexList() { + return null; + } + + public final Pattern getAfterRegex() { + return AfterRegex; + } + + public final Pattern getBeforeRegex() { + return BeforeRegex; + } + + public final Pattern getSinceRegex() { + return SinceRegex; + } + + public final Pattern getAroundRegex() { + return AroundRegex; + } + + public final Pattern getFromToRegex() { + return FromToRegex; + } + + public final Pattern getSingleAmbiguousMonthRegex() { + return SingleAmbiguousMonthRegex; + } + + public final Pattern getPrepositionSuffixRegex() { + return PrepositionSuffixRegex; + } + + public final Pattern getAmbiguousRangeModifierPrefix() { + return null; + } + + public final Pattern getPotentialAmbiguousRangeRegex() { + return null; + } + + public final Pattern getNumberEndingPattern() { + return NumberEndingPattern; + } + + public final Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public final Pattern getUnspecificDatePeriodRegex() { + return UnspecificDatePeriodRegex; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java new file mode 100644 index 000000000..64e4ce397 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java @@ -0,0 +1,113 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchSetExtractorConfiguration extends BaseOptionsConfiguration implements ISetExtractorConfiguration { + + public static final Pattern PeriodicRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PeriodicRegex); + public static final Pattern EachUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.EachUnitRegex); + public static final Pattern EachPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.EachPrefixRegex); + public static final Pattern EachDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.EachDayRegex); + // TODO + public static final Pattern BeforeEachDayRegex = null; + public static final Pattern SetWeekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SetWeekDayRegex); + public static final Pattern SetEachRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SetEachRegex); + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + public FrenchSetExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchSetExtractorConfiguration(final DateTimeOptions options) { + super(options); + + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + timeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor( + new FrenchDateTimePeriodExtractorConfiguration(options)); + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + public final IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final Pattern getLastRegex() { + return FrenchDateExtractorConfiguration.LastDateRegex; + } + + public final Pattern getEachPrefixRegex() { + return EachPrefixRegex; + } + + public final Pattern getPeriodicRegex() { + return PeriodicRegex; + } + + public final Pattern getEachUnitRegex() { + return EachUnitRegex; + } + + public final Pattern getEachDayRegex() { + return EachDayRegex; + } + + public final Pattern getBeforeEachDayRegex() { + return BeforeEachDayRegex; + } + + public final Pattern getSetWeekDayRegex() { + return SetWeekDayRegex; + } + + public final Pattern getSetEachRegex() { + return SetEachRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java new file mode 100644 index 000000000..cd823eb07 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java @@ -0,0 +1,87 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchTimeExtractorConfiguration extends BaseOptionsConfiguration implements ITimeExtractorConfiguration { + + public static final Pattern DescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DescRegex); + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.HourNumRegex); + public static final Pattern MinuteNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MinuteNumRegex); + + public static final Pattern OclockRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OclockRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmRegex); + + public static final Pattern LessThanOneHour = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanOneHour); + // public static final Pattern TensTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TensTimeRegex); + + public static final Pattern WrittenTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WrittenTimeRegex); + public static final Pattern TimePrefix = RegExpUtility.getSafeRegExp(FrenchDateTime.TimePrefix); + public static final Pattern TimeSuffix = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeSuffix); + public static final Pattern BasicTime = RegExpUtility.getSafeRegExp(FrenchDateTime.BasicTime); + public static final Pattern IshRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.IshRegex); + + public static final Pattern AtRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AtRegex); + public static final Pattern ConnectNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectNumRegex); + public static final Pattern TimeBeforeAfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeBeforeAfterRegex); + public static final Iterable TimeRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex1)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex2)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex3)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex4)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex5)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex6)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex7)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex8)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex9)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex10)); + add(ConnectNumRegex); + } + }; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + public FrenchTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchTimeExtractorConfiguration(final DateTimeOptions options) { + super(options); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + } + + public final Pattern getIshRegex() { + return IshRegex; + } + + public final Iterable getTimeRegexList() { + return TimeRegexList; + } + + public final Pattern getAtRegex() { + return AtRegex; + } + + public final Pattern getTimeBeforeAfterRegex() { + return TimeBeforeAfterRegex; + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..b8f093757 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java @@ -0,0 +1,150 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements ITimePeriodExtractorConfiguration { + + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.HourNumRegex); + public static final Pattern PureNumFromTo = RegExpUtility.getSafeRegExp(FrenchDateTime.PureNumFromTo); + public static final Pattern PureNumBetweenAnd = RegExpUtility.getSafeRegExp(FrenchDateTime.PureNumBetweenAnd); + public static final Pattern SpecificTimeFromTo = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecificTimeFromTo); + public static final Pattern SpecificTimeBetweenAnd = RegExpUtility + .getSafeRegExp(FrenchDateTime.SpecificTimeBetweenAnd); + // TODO: What are these? + // public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.UnitRegex); + // public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.FollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeNumberCombinedWithUnit); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeOfDayRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.GeneralEndingRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TillRegex); + private static final Pattern FromRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromRegex2); + private static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeConnectorRegex); + private static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BeforeRegex2); + public final IDateTimeExtractor timeZoneExtractor; + public final Iterable getSimpleCasesRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.AmRegex)); + } + }; + public final Iterable getPureNumberRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + } + }; + private final String tokenBeforeDate; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeExtractor singleTimeExtractor; + private final IExtractor integerExtractor; + + public FrenchTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchTimePeriodExtractorConfiguration(final DateTimeOptions options) { + + super(options); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + singleTimeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + integerExtractor = new IntegerExtractor(); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + } + + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + public final IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + public final IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + public Iterable getSimpleCasesRegex() { + return getSimpleCasesRegex; + } + + public Iterable getPureNumberRegex() { + return getPureNumberRegex; + } + + public final Pattern getTillRegex() { + return TillRegex; + } + + public final Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + public final Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public ResultIndex getFromTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(final String text) { + final Optional match = Arrays + .stream(RegExpUtility.getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorAndRegex), text)) + .findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..4ad339474 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchTimeZoneExtractorConfiguration extends BaseOptionsConfiguration implements ITimeZoneExtractorConfiguration { + public FrenchTimeZoneExtractorConfiguration(final DateTimeOptions options) { + super(options); + + } + + private Pattern directUtcRegex; + + public final Pattern getDirectUtcRegex() { + return directUtcRegex; + } + + private Pattern locationTimeSuffixRegex; + + public final Pattern getLocationTimeSuffixRegex() { + return locationTimeSuffixRegex; + } + + private StringMatcher locationMatcher; + + public final StringMatcher getLocationMatcher() { + return locationMatcher; + } + + private StringMatcher timeZoneMatcher; + + public final StringMatcher getTimeZoneMatcher() { + return timeZoneMatcher; + } + + public final ArrayList getAmbiguousTimezoneList() { + return new ArrayList<>(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..580fa2346 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java @@ -0,0 +1,292 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDatePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeAltParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDurationParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.BaseDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; + +public class FrenchCommonDateTimeParserConfiguration extends BaseDateParserConfiguration { + + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap numbers; + private final ImmutableMap doubleNumbers; + private final ImmutableMap writtenDecades; + private final ImmutableMap specialDecadeCases; + + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + private final IDateTimeParser timeZoneParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser datePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser dateTimeAltParser; + + public FrenchCommonDateTimeParserConfiguration(final DateTimeOptions options) { + + super(options); + + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + + unitMap = FrenchDateTime.UnitMap; + unitValueMap = FrenchDateTime.UnitValueMap; + seasonMap = FrenchDateTime.SeasonMap; + specialYearPrefixesMap = FrenchDateTime.SpecialYearPrefixesMap; + cardinalMap = FrenchDateTime.CardinalMap; + dayOfWeek = FrenchDateTime.DayOfWeek; + monthOfYear = FrenchDateTime.MonthOfYear; + numbers = FrenchDateTime.Numbers; + doubleNumbers = FrenchDateTime.DoubleNumbers; + writtenDecades = FrenchDateTime.WrittenDecades; + specialDecadeCases = FrenchDateTime.SpecialDecadeCases; + + cardinalExtractor = CardinalExtractor.getInstance(); + integerExtractor = new IntegerExtractor(); + ordinalExtractor = new OrdinalExtractor(); + + numberParser = new BaseNumberParser(new FrenchNumberParserConfiguration()); + + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + dateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor( + new FrenchDateTimePeriodExtractorConfiguration(options)); + + timeZoneParser = new BaseTimeZoneParser(); + durationParser = new BaseDurationParser(new FrenchDurationParserConfiguration(this)); + dateParser = new BaseDateParser(new FrenchDateParserConfiguration(this)); + timeParser = new FrenchTimeParser(new FrenchTimeParserConfiguration(this)); + dateTimeParser = new BaseDateTimeParser(new FrenchDateTimeParserConfiguration(this)); + datePeriodParser = new BaseDatePeriodParser(new FrenchDatePeriodParserConfiguration(this)); + timePeriodParser = new BaseTimePeriodParser(new FrenchTimePeriodParserConfiguration(this)); + dateTimePeriodParser = new BaseDateTimePeriodParser(new FrenchDateTimePeriodParserConfiguration(this)); + dateTimeAltParser = new BaseDateTimeAltParser(new FrenchDateTimeAltParserConfiguration(this)); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + @Override + public IDateTimeParser getDateTimeAltParser() { + return dateTimeAltParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return ImmutableMap.builder() + .putAll(BaseDateTime.DayOfMonthDictionary) + .putAll(FrenchDateTime.DayOfMonth).build(); + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java new file mode 100644 index 000000000..0dfa50978 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java @@ -0,0 +1,336 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.StringExtension; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchDateParserConfiguration extends BaseOptionsConfiguration implements IDateParserConfiguration { + + private final String dateTokenPrefix; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IExtractor cardinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeParser durationParser; + private final ImmutableMap unitMap; + private final Iterable dateRegexes; + private final Pattern onRegex; + private final Pattern specialDayRegex; + private final Pattern specialDayWithNumRegex; + private final Pattern nextRegex; + private final Pattern thisRegex; + private final Pattern lastRegex; + private final Pattern unitRegex; + private final Pattern weekDayRegex; + private final Pattern monthRegex; + private final Pattern weekDayOfMonthRegex; + private final Pattern forTheRegex; + private final Pattern weekDayAndDayOfMonthRegex; + private final Pattern relativeMonthRegex; + private final Pattern strictRelativeRegex; + private final Pattern yearSuffix; + private final Pattern relativeWeekDayRegex; + private final Pattern relativeDayRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + + private final ImmutableMap dayOfMonth; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap cardinalMap; + private final List sameDayTerms; + private final List plusOneDayTerms; + private final List plusTwoDayTerms; + private final List minusOneDayTerms; + private final List minusTwoDayTerms; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchDateParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + dateTokenPrefix = FrenchDateTime.DateTokenPrefix; + integerExtractor = config.getIntegerExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateRegexes = Collections.unmodifiableList(FrenchDateExtractorConfiguration.DateRegexList); + onRegex = FrenchDateExtractorConfiguration.OnRegex; + specialDayRegex = FrenchDateExtractorConfiguration.SpecialDayRegex; + specialDayWithNumRegex = FrenchDateExtractorConfiguration.SpecialDayWithNumRegex; + nextRegex = FrenchDateExtractorConfiguration.NextDateRegex; + thisRegex = FrenchDateExtractorConfiguration.ThisRegex; + lastRegex = FrenchDateExtractorConfiguration.LastDateRegex; + unitRegex = FrenchDateExtractorConfiguration.DateUnitRegex; + weekDayRegex = FrenchDateExtractorConfiguration.WeekDayRegex; + monthRegex = FrenchDateExtractorConfiguration.MonthRegex; + weekDayOfMonthRegex = FrenchDateExtractorConfiguration.WeekDayOfMonthRegex; + forTheRegex = FrenchDateExtractorConfiguration.ForTheRegex; + weekDayAndDayOfMonthRegex = FrenchDateExtractorConfiguration.WeekDayAndDayOfMonthRegex; + relativeMonthRegex = FrenchDateExtractorConfiguration.RelativeMonthRegex; + strictRelativeRegex = FrenchDateExtractorConfiguration.StrictRelativeRegex; + yearSuffix = FrenchDateExtractorConfiguration.YearSuffix; + relativeWeekDayRegex = FrenchDateExtractorConfiguration.RelativeWeekDayRegex; + relativeDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeDayRegex); + nextPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PreviousPrefixRegex); + dayOfMonth = config.getDayOfMonth(); + dayOfWeek = config.getDayOfWeek(); + monthOfYear = config.getMonthOfYear(); + cardinalMap = config.getCardinalMap(); + unitMap = config.getUnitMap(); + utilityConfiguration = config.getUtilityConfiguration(); + sameDayTerms = Collections.unmodifiableList(FrenchDateTime.SameDayTerms); + plusOneDayTerms = Collections.unmodifiableList(FrenchDateTime.PlusOneDayTerms); + plusTwoDayTerms = Collections.unmodifiableList(FrenchDateTime.PlusTwoDayTerms); + minusOneDayTerms = Collections.unmodifiableList(FrenchDateTime.MinusOneDayTerms); + minusTwoDayTerms = Collections.unmodifiableList(FrenchDateTime.MinusTwoDayTerms); + } + + @Override + public String getDateTokenPrefix() { + return dateTokenPrefix; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Iterable getDateRegexes() { + return dateRegexes; + } + + @Override + public Pattern getOnRegex() { + return onRegex; + } + + @Override + public Pattern getSpecialDayRegex() { + return specialDayRegex; + } + + @Override + public Pattern getSpecialDayWithNumRegex() { + return specialDayWithNumRegex; + } + + @Override + public Pattern getNextRegex() { + return nextRegex; + } + + @Override + public Pattern getThisRegex() { + return thisRegex; + } + + @Override + public Pattern getLastRegex() { + return lastRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getMonthRegex() { + return monthRegex; + } + + @Override + public Pattern getWeekDayOfMonthRegex() { + return weekDayOfMonthRegex; + } + + @Override + public Pattern getForTheRegex() { + return forTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return weekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return relativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return strictRelativeRegex; + } + + @Override + public Pattern getYearSuffix() { + return yearSuffix; + } + + @Override + public Pattern getRelativeWeekDayRegex() { + return relativeWeekDayRegex; + } + + @Override + public Pattern getRelativeDayRegex() { + return relativeDayRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public List getSameDayTerms() { + return sameDayTerms; + } + + @Override + public List getPlusOneDayTerms() { + return plusOneDayTerms; + } + + @Override + public List getMinusOneDayTerms() { + return minusOneDayTerms; + } + + @Override + public List getPlusTwoDayTerms() { + return plusTwoDayTerms; + } + + @Override + public List getMinusTwoDayTerms() { + return minusTwoDayTerms; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Integer getSwiftMonthOrYear(final String text) { + final String trimmedText = text.trim().toLowerCase(Locale.ROOT); + int swift = 0; + + Matcher regexMatcher = nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = 1; + } + + regexMatcher = previousPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = -1; + } + + return swift; + } + + @Override + public Boolean isCardinalLast(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + return trimmedText.endsWith("dernière") || trimmedText.endsWith("dernières") || trimmedText.endsWith( + "derniere") || trimmedText.endsWith("dernieres"); + } + + @Override + public String normalize(final String text) { + return StringExtension.normalize(text, ImmutableMap.of()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java new file mode 100644 index 000000000..f3c1e4514 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java @@ -0,0 +1,559 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class FrenchDatePeriodParserConfiguration extends BaseOptionsConfiguration implements IDatePeriodParserConfiguration { + + public static final Pattern nextPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextPrefixRegex); + public static final Pattern previousPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PreviousPrefixRegex); + public static final Pattern thisPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ThisPrefixRegex); + public static final Pattern nextSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextSuffixRegex); + public static final Pattern pastSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PastSuffixRegex); + public static final Pattern relativeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeRegex); + public static final Pattern unspecificEndOfRangeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.UnspecificEndOfRangeRegex); + private final String tokenBeforeDate; + + // Regex + private final IDateExtractor dateExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor integerExtractor; + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser durationParser; + private final Pattern monthFrontBetweenRegex; + private final Pattern betweenRegex; + private final Pattern monthFrontSimpleCasesRegex; + private final Pattern simpleCasesRegex; + private final Pattern oneWordPeriodRegex; + private final Pattern monthWithYear; + private final Pattern monthNumWithYear; + private final Pattern yearRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnit; + private final Pattern weekOfMonthRegex; + private final Pattern weekOfYearRegex; + private final Pattern quarterRegex; + private final Pattern quarterRegexYearFront; + private final Pattern allHalfYearRegex; + private final Pattern seasonRegex; + private final Pattern whichWeekRegex; + private final Pattern weekOfRegex; + private final Pattern monthOfRegex; + private final Pattern inConnectorRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern restOfDateRegex; + private final Pattern laterEarlyPeriodRegex; + private final Pattern weekWithWeekDayRangeRegex; + private final Pattern yearPlusNumberRegex; + private final Pattern decadeWithCenturyRegex; + private final Pattern yearPeriodRegex; + private final Pattern complexDatePeriodRegex; + private final Pattern relativeDecadeRegex; + private final Pattern referenceDatePeriodRegex; + private final Pattern agoRegex; + private final Pattern laterRegex; + private final Pattern lessThanRegex; + private final Pattern moreThanRegex; + private final Pattern centurySuffixRegex; + private final Pattern nowRegex; + // Dictionaries + private final ImmutableMap unitMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap writtenDecades; + private final ImmutableMap numbers; + private final ImmutableMap specialDecadeCases; + + public FrenchDatePeriodParserConfiguration(final ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + cardinalExtractor = config.getCardinalExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateParser = config.getDateParser(); + monthFrontBetweenRegex = FrenchDatePeriodExtractorConfiguration.MonthFrontBetweenRegex; + betweenRegex = FrenchDatePeriodExtractorConfiguration.BetweenRegex; + monthFrontSimpleCasesRegex = FrenchDatePeriodExtractorConfiguration.MonthFrontSimpleCasesRegex; + simpleCasesRegex = FrenchDatePeriodExtractorConfiguration.SimpleCasesRegex; + oneWordPeriodRegex = FrenchDatePeriodExtractorConfiguration.OneWordPeriodRegex; + monthWithYear = FrenchDatePeriodExtractorConfiguration.MonthWithYearRegex; + monthNumWithYear = FrenchDatePeriodExtractorConfiguration.MonthNumWithYearRegex; + yearRegex = FrenchDatePeriodExtractorConfiguration.YearRegex; + pastRegex = FrenchDatePeriodExtractorConfiguration.PastRegex; + futureRegex = FrenchDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = FrenchDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnit = FrenchDurationExtractorConfiguration.NumberCombinedWithUnit; + weekOfMonthRegex = FrenchDatePeriodExtractorConfiguration.WeekOfMonthRegex; + weekOfYearRegex = FrenchDatePeriodExtractorConfiguration.WeekOfYearRegex; + quarterRegex = FrenchDatePeriodExtractorConfiguration.QuarterRegex; + quarterRegexYearFront = FrenchDatePeriodExtractorConfiguration.QuarterRegexYearFront; + allHalfYearRegex = FrenchDatePeriodExtractorConfiguration.AllHalfYearRegex; + seasonRegex = FrenchDatePeriodExtractorConfiguration.SeasonRegex; + whichWeekRegex = FrenchDatePeriodExtractorConfiguration.WhichWeekRegex; + weekOfRegex = FrenchDatePeriodExtractorConfiguration.WeekOfRegex; + monthOfRegex = FrenchDatePeriodExtractorConfiguration.MonthOfRegex; + restOfDateRegex = FrenchDatePeriodExtractorConfiguration.RestOfDateRegex; + laterEarlyPeriodRegex = FrenchDatePeriodExtractorConfiguration.LaterEarlyPeriodRegex; + weekWithWeekDayRangeRegex = FrenchDatePeriodExtractorConfiguration.WeekWithWeekDayRangeRegex; + yearPlusNumberRegex = FrenchDatePeriodExtractorConfiguration.YearPlusNumberRegex; + decadeWithCenturyRegex = FrenchDatePeriodExtractorConfiguration.DecadeWithCenturyRegex; + yearPeriodRegex = FrenchDatePeriodExtractorConfiguration.YearPeriodRegex; + complexDatePeriodRegex = FrenchDatePeriodExtractorConfiguration.ComplexDatePeriodRegex; + relativeDecadeRegex = FrenchDatePeriodExtractorConfiguration.RelativeDecadeRegex; + inConnectorRegex = config.getUtilityConfiguration().getInConnectorRegex(); + withinNextPrefixRegex = FrenchDatePeriodExtractorConfiguration.WithinNextPrefixRegex; + referenceDatePeriodRegex = FrenchDatePeriodExtractorConfiguration.ReferenceDatePeriodRegex; + agoRegex = FrenchDatePeriodExtractorConfiguration.AgoRegex; + laterRegex = FrenchDatePeriodExtractorConfiguration.LaterRegex; + lessThanRegex = FrenchDatePeriodExtractorConfiguration.LessThanRegex; + moreThanRegex = FrenchDatePeriodExtractorConfiguration.MoreThanRegex; + centurySuffixRegex = FrenchDatePeriodExtractorConfiguration.CenturySuffixRegex; + nowRegex = FrenchDatePeriodExtractorConfiguration.NowRegex; + + unitMap = config.getUnitMap(); + cardinalMap = config.getCardinalMap(); + dayOfMonth = config.getDayOfMonth(); + monthOfYear = config.getMonthOfYear(); + seasonMap = config.getSeasonMap(); + specialYearPrefixesMap = config.getSpecialYearPrefixesMap(); + numbers = config.getNumbers(); + writtenDecades = config.getWrittenDecades(); + specialDecadeCases = config.getSpecialDecadeCases(); + } + + @Override + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public final IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public final IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public final IParser getNumberParser() { + return numberParser; + } + + @Override + public final IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public final Pattern getMonthFrontBetweenRegex() { + return monthFrontBetweenRegex; + } + + @Override + public final Pattern getBetweenRegex() { + return betweenRegex; + } + + @Override + public final Pattern getMonthFrontSimpleCasesRegex() { + return monthFrontSimpleCasesRegex; + } + + @Override + public final Pattern getSimpleCasesRegex() { + return simpleCasesRegex; + } + + @Override + public final Pattern getOneWordPeriodRegex() { + return oneWordPeriodRegex; + } + + @Override + public final Pattern getMonthWithYear() { + return monthWithYear; + } + + @Override + public final Pattern getMonthNumWithYear() { + return monthNumWithYear; + } + + @Override + public final Pattern getYearRegex() { + return yearRegex; + } + + @Override + public final Pattern getPastRegex() { + return pastRegex; + } + + @Override + public final Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public final Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public final Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public final Pattern getWeekOfMonthRegex() { + return weekOfMonthRegex; + } + + @Override + public final Pattern getWeekOfYearRegex() { + return weekOfYearRegex; + } + + @Override + public final Pattern getQuarterRegex() { + return quarterRegex; + } + + @Override + public final Pattern getQuarterRegexYearFront() { + return quarterRegexYearFront; + } + + @Override + public final Pattern getAllHalfYearRegex() { + return allHalfYearRegex; + } + + @Override + public final Pattern getSeasonRegex() { + return seasonRegex; + } + + @Override + public final Pattern getWhichWeekRegex() { + return whichWeekRegex; + } + + @Override + public final Pattern getWeekOfRegex() { + return weekOfRegex; + } + + @Override + public final Pattern getMonthOfRegex() { + return monthOfRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return inConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public final Pattern getRestOfDateRegex() { + return restOfDateRegex; + } + + @Override + public final Pattern getLaterEarlyPeriodRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getWeekWithWeekDayRangeRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getYearPlusNumberRegex() { + return yearPlusNumberRegex; + } + + @Override + public final Pattern getDecadeWithCenturyRegex() { + return decadeWithCenturyRegex; + } + + @Override + public final Pattern getYearPeriodRegex() { + return yearPeriodRegex; + } + + @Override + public final Pattern getComplexDatePeriodRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getRelativeDecadeRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getReferenceDatePeriodRegex() { + return referenceDatePeriodRegex; + } + + @Override + public final Pattern getAgoRegex() { + return agoRegex; + } + + @Override + public final Pattern getLaterRegex() { + return laterRegex; + } + + @Override + public final Pattern getLessThanRegex() { + return lessThanRegex; + } + + @Override + public final Pattern getMoreThanRegex() { + return moreThanRegex; + } + + @Override + public final Pattern getCenturySuffixRegex() { + return centurySuffixRegex; + } + + @Override + public final Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public final Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public final Pattern getThisPrefixRegex() { + return thisPrefixRegex; + } + + @Override + public final Pattern getRelativeRegex() { + return relativeRegex; + } + + @Override + public final Pattern getUnspecificEndOfRangeRegex() { + return unspecificEndOfRangeRegex; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public int getSwiftDayOrMonth(final String text) { + + final String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + if (trimmedText.endsWith("prochain") || trimmedText.endsWith("prochaine")) { + swift = 1; + } + + if (trimmedText.endsWith("dernière") || + trimmedText.endsWith("dernières") || + trimmedText.endsWith("derniere") || + trimmedText.endsWith("dernieres") + ) { + swift = -1; + } + + return swift; + } + + @Override + public int getSwiftYear(final String text) { + + final String trimmedText = text.trim().toLowerCase(); + int swift = -10; + + if (trimmedText.endsWith("prochain") || trimmedText.endsWith("prochaine")) { + swift = 1; + } + + if (trimmedText.endsWith("dernière") || trimmedText.endsWith("dernières") || trimmedText + .endsWith("derniere") || trimmedText.endsWith("dernieres")) { + swift = -1; + } else if (trimmedText.startsWith("cette")) { + swift = 0; + } + + return swift; + } + + @Override + public boolean isFuture(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + return FrenchDateTime.FutureStartTerms.stream().anyMatch(o -> trimmedText.startsWith(o)) || FrenchDateTime.FutureEndTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isLastCardinal(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + final Optional matchLast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)) + .findFirst(); + return matchLast.isPresent(); + } + + @Override + public boolean isMonthOnly(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.MonthTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isMonthToDate(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.MonthToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isWeekend(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isWeekOnly(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + final boolean nextSuffix = Arrays.stream(RegExpUtility.getMatches(nextSuffixRegex, trimmedText)) + .findFirst().isPresent(); + final boolean pastSuffix = Arrays.stream(RegExpUtility.getMatches(pastSuffixRegex, trimmedText)) + .findFirst().isPresent(); + + return (FrenchDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + (FrenchDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.contains(o)) && (nextSuffix || pastSuffix))) && + !FrenchDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearOnly(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.YearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearToDate(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + return FrenchDateTime.YearToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..1911f9926 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; + +public class FrenchDateTimeAltParserConfiguration implements IDateTimeAltParserConfiguration { + + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser datePeriodParser; + + public FrenchDateTimeAltParserConfiguration(final ICommonDateTimeParserConfiguration config) { + dateTimeParser = config.getDateTimeParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + datePeriodParser = config.getDatePeriodParser(); + } + + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public IDateTimeParser getDateParser() { + return dateParser; + } + + public IDateTimeParser getTimeParser() { + return timeParser; + } + + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java new file mode 100644 index 000000000..911f0436e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java @@ -0,0 +1,258 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDateTimeParserConfiguration extends BaseOptionsConfiguration implements IDateTimeParserConfiguration { + + public final String tokenBeforeDate; + public final String tokenBeforeTime; + + public final IDateTimeExtractor dateExtractor; + public final IDateTimeExtractor timeExtractor; + public final IDateTimeParser dateParser; + public final IDateTimeParser timeParser; + public final IExtractor cardinalExtractor; + public final IExtractor integerExtractor; + public final IParser numberParser; + public final IDateTimeExtractor durationExtractor; + public final IDateTimeParser durationParser; + + public final ImmutableMap unitMap; + public final ImmutableMap numbers; + + public final Pattern nowRegex; + public final Pattern amTimeRegex; + public final Pattern pmTimeRegex; + public final Pattern simpleTimeOfTodayAfterRegex; + public final Pattern simpleTimeOfTodayBeforeRegex; + public final Pattern specificTimeOfDayRegex; + public final Pattern specificEndOfRegex; + public final Pattern unspecificEndOfRegex; + public final Pattern unitRegex; + public final Pattern dateNumberConnectorRegex; + + public final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchDateTimeParserConfiguration(final ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + durationParser = config.getDurationParser(); + integerExtractor = config.getIntegerExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + utilityConfiguration = config.getUtilityConfiguration(); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + tokenBeforeTime = FrenchDateTime.TokenBeforeTime; + + nowRegex = FrenchDateTimeExtractorConfiguration.NowRegex; + unitRegex = FrenchDateTimeExtractorConfiguration.UnitRegex; + specificEndOfRegex = FrenchDateTimeExtractorConfiguration.SpecificEndOfRegex; + unspecificEndOfRegex = FrenchDateTimeExtractorConfiguration.UnspecificEndOfRegex; + specificTimeOfDayRegex = FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + dateNumberConnectorRegex = FrenchDateTimeExtractorConfiguration.DateNumberConnectorRegex; + simpleTimeOfTodayAfterRegex = FrenchDateTimeExtractorConfiguration.SimpleTimeOfTodayAfterRegex; + simpleTimeOfTodayBeforeRegex = FrenchDateTimeExtractorConfiguration.SimpleTimeOfTodayBeforeRegex; + + pmTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex); + amTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AMTimeRegex); + } + + @Override + public int getHour(final String text, final int hour) { + int result = hour; + + final String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.endsWith("matin") && hour >= Constants.HalfDayHourCount) { + result -= Constants.HalfDayHourCount; + } else if (!trimmedText.endsWith("matin") && hour < Constants.HalfDayHourCount) { + result += Constants.HalfDayHourCount; + } + + return result; + } + + @Override + public ResultTimex getMatchedNowTimex(final String text) { + + final String trimmedText = text.trim().toLowerCase(); + + final String timex; + if (trimmedText.endsWith("maintenant")) { + timex = "PRESENT_REF"; + } else if (trimmedText.equals("récemment") || trimmedText.equals("précédemment") || trimmedText + .equals("auparavant")) { + timex = "PAST_REF"; + } else if (trimmedText.equals("dès que possible") || trimmedText.equals("dqp")) { + timex = "FUTURE_REF"; + } else { + timex = null; + return new ResultTimex(false, null); + } + + return new ResultTimex(true, timex); + } + + @Override + public int getSwiftDay(final String text) { + int swift = 0; + + final String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.startsWith("prochain") || trimmedText.startsWith("prochain") || + trimmedText.startsWith("prochaine") || trimmedText.startsWith("prochaine")) { + swift = 1; + } else if (trimmedText.startsWith("dernier") || trimmedText.startsWith("dernière") || + trimmedText.startsWith("dernier") || trimmedText.startsWith("dernière")) { + swift = -1; + } + + return swift; + + } + + @Override + public boolean containsAmbiguousToken(final String text, final String matchedText) { + return false; + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public String getTokenBeforeTime() { + return tokenBeforeTime; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + public Pattern getAMTimeRegex() { + return amTimeRegex; + } + + public Pattern getPMTimeRegex() { + return pmTimeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return simpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return simpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return specificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return unspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return dateNumberConnectorRegex; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..b4c67d729 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java @@ -0,0 +1,331 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDateTimePeriodParserConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodParserConfiguration { + + private final String tokenBeforeDate; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor cardinalExtractor; + + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern timeOfDayRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnitRegex; + private final Pattern unitRegex; + private final Pattern periodTimeOfDayWithDateRegex; + private final Pattern relativeTimeUnitRegex; + private final Pattern restOfDateTimeRegex; + private final Pattern amDescRegex; + private final Pattern pmDescRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern prefixDayRegex; + private final Pattern beforeRegex; + private final Pattern afterRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + + public FrenchDateTimePeriodParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + numberParser = config.getNumberParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + timePeriodParser = config.getTimePeriodParser(); + durationParser = config.getDurationParser(); + dateTimeParser = config.getDateTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + + pureNumberFromToRegex = FrenchTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = FrenchTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeOfDayRegex = FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + timeOfDayRegex = FrenchDateTimeExtractorConfiguration.TimeOfDayRegex; + pastRegex = FrenchDatePeriodExtractorConfiguration.PastRegex; + futureRegex = FrenchDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = FrenchDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnitRegex = FrenchDateTimePeriodExtractorConfiguration.NumberCombinedWithUnit; + unitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + periodTimeOfDayWithDateRegex = FrenchDateTimePeriodExtractorConfiguration.PeriodTimeOfDayWithDateRegex; + relativeTimeUnitRegex = FrenchDateTimePeriodExtractorConfiguration.RelativeTimeUnitRegex; + restOfDateTimeRegex = FrenchDateTimePeriodExtractorConfiguration.RestOfDateTimeRegex; + amDescRegex = FrenchDateTimePeriodExtractorConfiguration.AmDescRegex; + pmDescRegex = FrenchDateTimePeriodExtractorConfiguration.PmDescRegex; + withinNextPrefixRegex = FrenchDateTimePeriodExtractorConfiguration.WithinNextPrefixRegex; + prefixDayRegex = FrenchDateTimePeriodExtractorConfiguration.PrefixDayRegex; + beforeRegex = FrenchDateTimePeriodExtractorConfiguration.BeforeRegex; + afterRegex = FrenchDateTimePeriodExtractorConfiguration.AfterRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnitRegex() { + return numberCombinedWithUnitRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return periodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return relativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return restOfDateTimeRegex; + } + + @Override + public Pattern getAmDescRegex() { + return amDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return pmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return prefixDayRegex; + } + + @Override + public Pattern getBeforeRegex() { + return beforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return afterRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public MatchedTimeRangeResult getMatchedTimeRange(final String text, + String timeStr, + int beginHour, + int endHour, + int endMin) { + beginHour = 0; + endHour = 0; + endMin = 0; + + final String trimmedText = text.trim().toLowerCase(); + + if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.MorningStartEndRegex), trimmedText).length > 0) { + timeStr = "TMO"; + beginHour = 8; + endHour = Constants.HalfDayHourCount; + } else if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.AfternoonStartEndRegex), trimmedText).length + > 0) { + timeStr = "TAF"; + beginHour = Constants.HalfDayHourCount; + endHour = 16; + } else if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.EveningStartEndRegex), trimmedText).length > 0) { + timeStr = "TEV"; + beginHour = 16; + endHour = 20; + } else if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.NightStartEndRegex), trimmedText).length > 0) { + timeStr = "TNI"; + beginHour = 20; + endHour = 23; + endMin = 59; + } else { + return new MatchedTimeRangeResult(false, null, beginHour, endHour, endMin); + } + + return new MatchedTimeRangeResult(true, timeStr, beginHour, endHour, endMin); + } + + @Override + public int getSwiftPrefix(final String text) { + final String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + if (trimmedText.startsWith("prochain") || trimmedText.endsWith("prochain") || + trimmedText.startsWith("prochaine") || trimmedText.endsWith("prochaine")) { + swift = 1; + } else if (trimmedText.startsWith("derniere") || trimmedText.startsWith("dernier") || + trimmedText.endsWith("derniere") || trimmedText.endsWith("dernier")) { + swift = -1; + } + + return swift; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java new file mode 100644 index 000000000..cf69ffcbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java @@ -0,0 +1,144 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; +import java.util.regex.Pattern; + +public class FrenchDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { + + private final IExtractor cardinalExtractor; + private final IExtractor durationExtractor; + private final IParser numberParser; + + private final Pattern numberCombinedWithUnit; + private final Pattern anUnitRegex; + private final Pattern duringRegex; + private final Pattern allDateUnitRegex; + private final Pattern halfDateUnitRegex; + private final Pattern suffixAndRegex; + private final Pattern followedUnit; + private final Pattern conjunctionRegex; + private final Pattern inexactNumberRegex; + private final Pattern inexactNumberUnitRegex; + private final Pattern durationUnitRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap doubleNumbers; + + public FrenchDurationParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(), false); + numberCombinedWithUnit = FrenchDurationExtractorConfiguration.NumberCombinedWithUnit; + + anUnitRegex = FrenchDurationExtractorConfiguration.AnUnitRegex; + duringRegex = FrenchDurationExtractorConfiguration.DuringRegex; + allDateUnitRegex = FrenchDurationExtractorConfiguration.AllRegex; + halfDateUnitRegex = FrenchDurationExtractorConfiguration.HalfRegex; + suffixAndRegex = FrenchDurationExtractorConfiguration.SuffixAndRegex; + followedUnit = FrenchDurationExtractorConfiguration.FollowedUnit; + conjunctionRegex = FrenchDurationExtractorConfiguration.ConjunctionRegex; + inexactNumberRegex = FrenchDurationExtractorConfiguration.InexactNumberRegex; + inexactNumberUnitRegex = FrenchDurationExtractorConfiguration.InexactNumberUnitRegex; + durationUnitRegex = FrenchDurationExtractorConfiguration.DurationUnitRegex; + + unitMap = config.getUnitMap(); + unitValueMap = config.getUnitValueMap(); + doubleNumbers = config.getDoubleNumbers(); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return anUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return duringRegex; + } + + @Override + public Pattern getAllDateUnitRegex() { + return allDateUnitRegex; + } + + @Override + public Pattern getHalfDateUnitRegex() { + return halfDateUnitRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return suffixAndRegex; + } + + @Override + public Pattern getFollowedUnit() { + return followedUnit; + } + + @Override + public Pattern getConjunctionRegex() { + return conjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return inexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return inexactNumberUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return durationUnitRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java new file mode 100644 index 000000000..d1cae9337 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java @@ -0,0 +1,226 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.IntFunction; + +public class FrenchHolidayParserConfiguration extends BaseHolidayParserConfiguration { + + public FrenchHolidayParserConfiguration() { + + setHolidayRegexList(FrenchHolidayExtractorConfiguration.HolidayRegexList); + + final HashMap> holidayNamesMap = new HashMap<>(); + for (final Map.Entry entry : FrenchDateTime.HolidayNames.entrySet()) { + if (entry.getValue() instanceof String[]) { + holidayNamesMap.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + setHolidayNames(ImmutableMap.copyOf(holidayNamesMap)); + } + + private static LocalDateTime newYear(final int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 1); + } + + private static LocalDateTime newYearEve(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 31); + } + + private static LocalDateTime christmasDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 25); + } + + private static LocalDateTime christmasEve(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 24); + } + + private static LocalDateTime valentinesDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 14); + } + + private static LocalDateTime whiteLoverDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 14); + } + + private static LocalDateTime foolDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 1); + } + + private static LocalDateTime girlsDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 7); + } + + private static LocalDateTime treePlantDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 12); + } + + private static LocalDateTime femaleDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 8); + } + + private static LocalDateTime childrenDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 1); + } + + private static LocalDateTime youthDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 4); + } + + private static LocalDateTime teacherDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 9, 10); + } + + private static LocalDateTime singlesDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + private static LocalDateTime maoBirthday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 26); + } + + private static LocalDateTime inaugurationDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 20); + } + + private static LocalDateTime groundhogDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 2); + } + + private static LocalDateTime stPatrickDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 17); + } + + private static LocalDateTime stGeorgeDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 23); + } + + private static LocalDateTime mayday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + private static LocalDateTime cincoDeMayoday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 5); + } + + private static LocalDateTime baptisteDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 24); + } + + private static LocalDateTime usaIndependenceDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 4); + } + + private static LocalDateTime bastilleDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 14); + } + + private static LocalDateTime halloweenDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 10, 31); + } + + private static LocalDateTime allHallowDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 1); + } + + private static LocalDateTime allSoulsday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 2); + } + + private static LocalDateTime guyFawkesDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 5); + } + + private static LocalDateTime veteransday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + protected static LocalDateTime fathersDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 17); + } + + protected static LocalDateTime mothersDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 27); + } + + protected static LocalDateTime labourDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + @Override + public int getSwiftYear(final String text) { + final String trimmedText = text.trim(); + int swift = -10; + if (trimmedText.endsWith("prochain")) { + // next - 'l'annee prochain' + swift = 1; + } else if (trimmedText.endsWith("dernier")) { + // last - 'l'annee dernier' + swift = -1; + } else if (trimmedText.endsWith("cette")) { + // this - 'cette annees' + swift = 0; + } + + return swift; + } + + public String sanitizeHolidayToken(final String holiday) { + return holiday + .replaceAll(" ", "") + .replaceAll("'", ""); + } + + protected HashMap> initHolidayFuncs() { + return new HashMap>(super.initHolidayFuncs()) {{ + put("maosbirthday", FrenchHolidayParserConfiguration::maoBirthday); + put("yuandan", FrenchHolidayParserConfiguration::newYear); + put("teachersday", FrenchHolidayParserConfiguration::teacherDay); + put("singleday", FrenchHolidayParserConfiguration::singlesDay); + put("allsaintsday", FrenchHolidayParserConfiguration::halloweenDay); + put("youthday", FrenchHolidayParserConfiguration::youthDay); + put("childrenday", FrenchHolidayParserConfiguration::childrenDay); + put("femaleday", FrenchHolidayParserConfiguration::femaleDay); + put("treeplantingday", FrenchHolidayParserConfiguration::treePlantDay); + put("arborday", FrenchHolidayParserConfiguration::treePlantDay); + put("girlsday", FrenchHolidayParserConfiguration::girlsDay); + put("whiteloverday", FrenchHolidayParserConfiguration::whiteLoverDay); + put("loverday", FrenchHolidayParserConfiguration::valentinesDay); + put("christmas", FrenchHolidayParserConfiguration::christmasDay); + put("xmas", FrenchHolidayParserConfiguration::christmasDay); + put("newyear", FrenchHolidayParserConfiguration::newYear); + put("newyearday", FrenchHolidayParserConfiguration::newYear); + put("newyearsday", FrenchHolidayParserConfiguration::newYear); + put("inaugurationday", FrenchHolidayParserConfiguration::inaugurationDay); + put("groundhougday", FrenchHolidayParserConfiguration::groundhogDay); + put("valentinesday", FrenchHolidayParserConfiguration::valentinesDay); + put("stpatrickday", FrenchHolidayParserConfiguration::stPatrickDay); + put("aprilfools", FrenchHolidayParserConfiguration::foolDay); + put("stgeorgeday", FrenchHolidayParserConfiguration::stGeorgeDay); + put("mayday", FrenchHolidayParserConfiguration::mayday); + put("cincodemayoday", FrenchHolidayParserConfiguration::cincoDeMayoday); + put("baptisteday", FrenchHolidayParserConfiguration::baptisteDay); + put("usindependenceday", FrenchHolidayParserConfiguration::usaIndependenceDay); + put("independenceday", FrenchHolidayParserConfiguration::usaIndependenceDay); + put("bastilleday", FrenchHolidayParserConfiguration::bastilleDay); + put("halloweenday", FrenchHolidayParserConfiguration::halloweenDay); + put("allhallowday", FrenchHolidayParserConfiguration::allHallowDay); + put("allsoulsday", FrenchHolidayParserConfiguration::allSoulsday); + put("guyfawkesday", FrenchHolidayParserConfiguration::guyFawkesDay); + put("veteransday", FrenchHolidayParserConfiguration::veteransday); + put("christmaseve", FrenchHolidayParserConfiguration::christmasEve); + put("newyeareve", FrenchHolidayParserConfiguration::newYearEve); + put("fathersday", FrenchHolidayParserConfiguration::fathersDay); + put("mothersday", FrenchHolidayParserConfiguration::mothersDay); + put("labourday", FrenchHolidayParserConfiguration::labourDay); + } + }; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java new file mode 100644 index 000000000..afd508109 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java @@ -0,0 +1,75 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseSetParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import java.util.regex.Pattern; + +public class FrenchMergedParserConfiguration extends FrenchCommonDateTimeParserConfiguration implements IMergedParserConfiguration { + + public FrenchMergedParserConfiguration(final DateTimeOptions options) { + super(options); + + beforeRegex = FrenchMergedExtractorConfiguration.BeforeRegex; + afterRegex = FrenchMergedExtractorConfiguration.AfterRegex; + sinceRegex = FrenchMergedExtractorConfiguration.SinceRegex; + aroundRegex = FrenchMergedExtractorConfiguration.AroundRegex; + suffixAfterRegex = FrenchMergedExtractorConfiguration.SuffixAfterRegex; + yearRegex = FrenchDatePeriodExtractorConfiguration.YearRegex; + superfluousWordMatcher = FrenchMergedExtractorConfiguration.SuperfluousWordMatcher; + + getParser = new BaseSetParser(new FrenchSetParserConfiguration(this)); + holidayParser = new BaseHolidayParser(new FrenchHolidayParserConfiguration()); + } + + private final Pattern beforeRegex; + private final Pattern afterRegex; + private final Pattern sinceRegex; + private final Pattern aroundRegex; + private final Pattern suffixAfterRegex; + private final Pattern yearRegex; + private final IDateTimeParser getParser; + private final IDateTimeParser holidayParser; + private final StringMatcher superfluousWordMatcher; + + public Pattern getBeforeRegex() { + return beforeRegex; + } + + public Pattern getAfterRegex() { + return afterRegex; + } + + public Pattern getSinceRegex() { + return sinceRegex; + } + + public Pattern getAroundRegex() { + return aroundRegex; + } + + public Pattern getSuffixAfterRegex() { + return suffixAfterRegex; + } + + public Pattern getYearRegex() { + return yearRegex; + } + + public IDateTimeParser getGetParser() { + return getParser; + } + + public IDateTimeParser getHolidayParser() { + return holidayParser; + } + + public StringMatcher getSuperfluousWordMatcher() { + return superfluousWordMatcher; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java new file mode 100644 index 000000000..b688497b0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java @@ -0,0 +1,195 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchSetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; +import java.util.regex.Pattern; + +public class FrenchSetParserConfiguration extends BaseOptionsConfiguration implements ISetParserConfiguration { + + private final IDateTimeExtractor durationExtractor; + private final IDateTimeParser durationParser; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IDateExtractor dateExtractor; + private final IDateTimeParser dateParser; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeParser dateTimeParser; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeParser datePeriodParser; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeParser timePeriodParser; + private final IDateTimeExtractor dateTimePeriodExtractor; + private final IDateTimeParser dateTimePeriodParser; + private final ImmutableMap unitMap; + private final Pattern eachPrefixRegex; + private final Pattern periodicRegex; + private final Pattern eachUnitRegex; + private final Pattern eachDayRegex; + private final Pattern setWeekDayRegex; + private final Pattern setEachRegex; + + public FrenchSetParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + durationExtractor = config.getDurationExtractor(); + timeExtractor = config.getTimeExtractor(); + dateExtractor = config.getDateExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + datePeriodExtractor = config.getDatePeriodExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + dateTimePeriodExtractor = config.getDateTimePeriodExtractor(); + + durationParser = config.getDurationParser(); + timeParser = config.getTimeParser(); + dateParser = config.getDateParser(); + dateTimeParser = config.getDateTimeParser(); + datePeriodParser = config.getDatePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + unitMap = config.getUnitMap(); + + eachPrefixRegex = FrenchSetExtractorConfiguration.EachPrefixRegex; + periodicRegex = FrenchSetExtractorConfiguration.PeriodicRegex; + eachUnitRegex = FrenchSetExtractorConfiguration.EachUnitRegex; + eachDayRegex = FrenchSetExtractorConfiguration.EachDayRegex; + setWeekDayRegex = FrenchSetExtractorConfiguration.SetWeekDayRegex; + setEachRegex = FrenchSetExtractorConfiguration.SetEachRegex; + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + public final IDateTimeParser getTimeParser() { + return timeParser; + } + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + public final IDateTimeParser getDateParser() { + return dateParser; + } + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + public final IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + public final IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + public final IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public final ImmutableMap getUnitMap() { + return unitMap; + } + + public final Pattern getEachPrefixRegex() { + return eachPrefixRegex; + } + + public final Pattern getPeriodicRegex() { + return periodicRegex; + } + + public final Pattern getEachUnitRegex() { + return eachUnitRegex; + } + + public final Pattern getEachDayRegex() { + return eachDayRegex; + } + + public final Pattern getSetWeekDayRegex() { + return setWeekDayRegex; + } + + public final Pattern getSetEachRegex() { + return setEachRegex; + } + + public MatchedTimexResult getMatchedDailyTimex(final String text) { + final String trimmedText = text.trim(); + final String timex; + if (trimmedText.equals("quotidien") || trimmedText.equals("quotidienne") || + trimmedText.equals("jours") || trimmedText.equals("journellement")) { + // daily + timex = "P1D"; + } else if (trimmedText.equals("hebdomadaire")) { + // weekly + timex = "P1W"; + } else if (trimmedText.equals("bihebdomadaire")) { + // bi weekly + timex = "P2W"; + } else if (trimmedText.equals("mensuel") || trimmedText.equals("mensuelle")) { + // monthly + timex = "P1M"; + } else if (trimmedText.equals("annuel") || trimmedText.equals("annuellement")) { + // yearly/annually + timex = "P1Y"; + } else { + return new MatchedTimexResult(false, null); + } + + return new MatchedTimexResult(true, timex); + } + + public MatchedTimexResult getMatchedUnitTimex(final String text) { + final String trimmedText = text.trim(); + final String timex; + if (trimmedText.equals("jour") || trimmedText.equals("journee")) { + timex = "P1D"; + } else if (trimmedText.equals("semaine")) { + timex = "P1W"; + } else if (trimmedText.equals("mois")) { + timex = "P1M"; + } else if (trimmedText.equals("an") || trimmedText.equals("annee")) { + // year + timex = "P1Y"; + } else { + return new MatchedTimexResult(false, null); + } + + return new MatchedTimexResult(true, timex); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java new file mode 100644 index 000000000..c66df2ae4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java @@ -0,0 +1,57 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.time.LocalDateTime; + +public class FrenchTimeParser extends BaseTimeParser { + + public FrenchTimeParser(final ITimeParserConfiguration config) { + super(config); + } + + @Override + protected DateTimeResolutionResult internalParse(final String text, final LocalDateTime referenceTime) { + DateTimeResolutionResult innerResult = super.internalParse(text, referenceTime); + + if (!innerResult.getSuccess()) { + innerResult = parseIsh(text, referenceTime); + } + + return innerResult; + } + + // parse "noonish", "11-ish" + private DateTimeResolutionResult parseIsh(final String text, final LocalDateTime referenceTime) { + final DateTimeResolutionResult result = new DateTimeResolutionResult(); + + final ConditionalMatch match = RegexExtension.matchExact(FrenchTimeExtractorConfiguration.IshRegex, text, true); + if (match.getSuccess()) { + final String hourStr = match.getMatch().get().getGroup(Constants.HourGroupName).value; + int hour = Constants.HalfDayHourCount; + + if (!StringUtility.isNullOrEmpty(hourStr)) { + hour = Integer.parseInt(hourStr); + } + + result.setTimex(String.format("T%02d", hour)); + final LocalDateTime resultTime = DateUtil.safeCreateFromMinValue( + referenceTime.getYear(), + referenceTime.getMonthValue(), + referenceTime.getDayOfMonth(), + hour, 0, 0); + result.setFutureValue(resultTime); + result.setPastValue(resultTime); + result.setSuccess(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java new file mode 100644 index 000000000..c33e542ba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java @@ -0,0 +1,156 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.util.regex.Pattern; + +public class FrenchTimeParserConfiguration extends BaseOptionsConfiguration implements ITimeParserConfiguration { + + public final Pattern atRegex; + private final Iterable timeRegexes; + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeParser timeZoneParser; + public String timeTokenPrefix = FrenchDateTime.TimeTokenPrefix; + public Pattern mealTimeRegex; + + public FrenchTimeParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + timeZoneParser = new BaseTimeZoneParser(); + + atRegex = FrenchTimeExtractorConfiguration.AtRegex; + timeRegexes = FrenchTimeExtractorConfiguration.TimeRegexList; + } + + @Override + public String getTimeTokenPrefix() { + return timeTokenPrefix; + } + + @Override + public Pattern getAtRegex() { + return atRegex; + } + + @Override + public Iterable getTimeRegexes() { + return timeRegexes; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public PrefixAdjustResult adjustByPrefix(final String prefix, int hour, int min, final boolean hasMin) { + + int deltaMin = 0; + final String trimmedPrefix = prefix.trim(); + + // c'este 8 heures et demie, - "it's half past 8" + if (trimmedPrefix.endsWith("demie")) { + deltaMin = 30; + } else if (trimmedPrefix.endsWith("un quart") || trimmedPrefix.endsWith("quart")) { + deltaMin = 15; + } else if (trimmedPrefix.endsWith("trois quarts")) { + deltaMin = 45; + } else { + final Match[] match = RegExpUtility + .getMatches(FrenchTimeExtractorConfiguration.LessThanOneHour, trimmedPrefix); + String minStr; + if (match.length > 0) { + minStr = match[0].getGroup("deltamin").value; + if (!StringUtility.isNullOrEmpty(minStr)) { + deltaMin = Integer.parseInt(minStr); + } else { + minStr = match[0].getGroup("deltaminnum").value; + deltaMin = numbers.get(minStr); + } + } + + } + + // 'to' i.e 'one to five' = 'un à cinq' + if (trimmedPrefix.endsWith("à")) { + deltaMin = -deltaMin; + } + + min += deltaMin; + if (min < 0) { + min += 60; + hour -= 1; + } + + return new PrefixAdjustResult(hour, min, true); + } + + @Override + public SuffixAdjustResult adjustBySuffix(final String suffix, + int hour, + final int min, + final boolean hasMin, + boolean hasAm, + boolean hasPm) { + + int deltaHour = 0; + final ConditionalMatch match = RegexExtension + .matchExact(FrenchTimeExtractorConfiguration.TimeSuffix, suffix, true); + + if (match.getSuccess()) { + final String oclockStr = match.getMatch().get().getGroup("heures").value; + if (StringUtility.isNullOrEmpty(oclockStr)) { + final String matchAmStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + if (!StringUtility.isNullOrEmpty(matchAmStr)) { + if (hour >= Constants.HalfDayHourCount) { + deltaHour = -Constants.HalfDayHourCount; + } + + hasAm = true; + } + + final String matchPmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + if (!StringUtility.isNullOrEmpty(matchPmStr)) { + if (hour < Constants.HalfDayHourCount) { + deltaHour = Constants.HalfDayHourCount; + } + + hasPm = true; + } + } + } + + hour = (hour + deltaHour) % 24; + + return new SuffixAdjustResult(hour, min, hasMin, hasAm, hasPm); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java new file mode 100644 index 000000000..acb8080a3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java @@ -0,0 +1,158 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.TimeOfDayResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import java.util.regex.Pattern; + +public class FrenchTimePeriodParserConfiguration extends BaseOptionsConfiguration implements ITimePeriodParserConfiguration { + + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IExtractor integerExtractor; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeFromToRegex; + private final Pattern specificTimeBetweenAndRegex; + private final Pattern timeOfDayRegex; + private final Pattern generalEndingRegex; + private final Pattern tillRegex; + + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchTimePeriodParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + integerExtractor = config.getIntegerExtractor(); + timeParser = config.getTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + pureNumberFromToRegex = FrenchTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = FrenchTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeFromToRegex = FrenchTimePeriodExtractorConfiguration.SpecificTimeFromTo; + specificTimeBetweenAndRegex = FrenchTimePeriodExtractorConfiguration.SpecificTimeBetweenAnd; + timeOfDayRegex = FrenchTimePeriodExtractorConfiguration.TimeOfDayRegex; + generalEndingRegex = FrenchTimePeriodExtractorConfiguration.GeneralEndingRegex; + tillRegex = FrenchTimePeriodExtractorConfiguration.TillRegex; + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeFromToRegex() { + return specificTimeFromToRegex; + } + + @Override + public Pattern getSpecificTimeBetweenAndRegex() { + return specificTimeBetweenAndRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return generalEndingRegex; + } + + @Override + public Pattern getTillRegex() { + return tillRegex; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public MatchedTimeRangeResult getMatchedTimexRange(final String text, + final String timex, + int beginHour, + int endHour, + int endMin) { + String mutatedText = text.trim(); + if (mutatedText.endsWith("s")) { + mutatedText = mutatedText.substring(0, mutatedText.length() - 1); + } + + final String trimmedText = mutatedText; + + beginHour = 0; + endHour = 0; + endMin = 0; + + String timeOfDay = ""; + if (FrenchDateTime.MorningTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Morning; + } else if (FrenchDateTime.AfternoonTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Afternoon; + } else if (FrenchDateTime.EveningTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Evening; + } else if (FrenchDateTime.DaytimeTermList.stream().anyMatch(o -> trimmedText.equals(o))) { + timeOfDay = Constants.Daytime; + } else if (FrenchDateTime.NightTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Night; + } else { + return new MatchedTimeRangeResult(false, null, beginHour, endHour, endMin); + } + + final TimeOfDayResolutionResult result = TimexUtility.parseTimeOfDay(timeOfDay); + + return new MatchedTimeRangeResult(true, result.getTimex(), result.getBeginHour(), result.getEndHour(), + result.getEndMin()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java new file mode 100644 index 000000000..7dba9ecea --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java @@ -0,0 +1,87 @@ +package com.microsoft.recognizers.text.datetime.french.utilities; + +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AgoRegex); + + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LaterRegex); + + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InConnectorRegex); + + public static final Pattern WithinNextPrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WithinNextPrefixRegex); + + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmDescRegex); + + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmDescRegex); + + public static final Pattern AmPmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmPmDescRegex); + + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeUnitRegex); + + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + + public static final Pattern CommonDatePrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.CommonDatePrefixRegex); + + @Override + public final Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public final Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public final Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public final Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public final Pattern getAmPmDescRegex() { + return AmPmDescRegex; + } + + @Override + public final Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public final Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public final Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public final Pattern getCommonDatePrefixRegex() { + return CommonDatePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java new file mode 100644 index 000000000..249271539 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java @@ -0,0 +1,94 @@ +package com.microsoft.recognizers.text.datetime.models; + +import com.microsoft.recognizers.text.ExtendedModelResult; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.utilities.FormatUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.stream.Collectors; + +public class DateTimeModel implements IModel { + + protected final IDateTimeExtractor extractor; + protected final IDateTimeParser parser; + + @Override + public String getModelTypeName() { + return Constants.MODEL_DATETIME; + } + + public DateTimeModel(IDateTimeParser parser, IDateTimeExtractor extractor) { + this.extractor = extractor; + this.parser = parser; + } + + @Override + public List parse(String query) { + return this.parse(query, LocalDateTime.now()); + } + + public List parse(String query, LocalDateTime reference) { + query = FormatUtility.preprocess(query); + + List parsedDateTimes = new ArrayList<>(); + + try { + List extractResults = extractor.extract(query, reference); + + for (ExtractResult result : extractResults) { + DateTimeParseResult parseResult = parser.parse(result, reference); + + if (parseResult.getValue() instanceof List) { + parsedDateTimes.addAll((List)parseResult.getValue()); + } else { + parsedDateTimes.add(parseResult); + } + } + + // Filter out ambiguous cases. Naïve approach. + parsedDateTimes = parser.filterResults(query, parsedDateTimes); + + } catch (Exception e) { + // Nothing to do. Exceptions in parse should not break users of recognizers. + // No result. + e.getMessage(); + } + + return parsedDateTimes.stream().map(this::getModelResult).collect(Collectors.toList()); + } + + private ModelResult getModelResult(DateTimeParseResult parsedDateTime) { + + int start = parsedDateTime.getStart(); + int end = parsedDateTime.getStart() + parsedDateTime.getLength() - 1; + String typeName = parsedDateTime.getType(); + SortedMap resolution = (SortedMap)parsedDateTime.getValue(); + String text = parsedDateTime.getText(); + + ModelResult result = new ModelResult(text, start, end, typeName, resolution); + + String[] types = parsedDateTime.getType().split("\\."); + String type = types[types.length - 1]; + if (type.equals(Constants.SYS_DATETIME_DATETIMEALT)) { + result = new ExtendedModelResult(result, getParentText(parsedDateTime)); + } + + return result; + } + + private String getParentText(DateTimeParseResult parsedDateTime) { + Map map = (Map)parsedDateTime.getData(); + Object result = map.get(ExtendedModelResult.ParentTextKey); + return String.valueOf(result); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java new file mode 100644 index 000000000..97a09e77b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java @@ -0,0 +1,739 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateContext; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseDateParser implements IDateTimeParser { + + private final IDateParserConfiguration config; + + public BaseDateParser(IDateParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATE; + } + + @Override + public ParseResult parse(ExtractResult extResult) { + return parse(extResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = this.parseBasicRegexMatch(er.getText(), referenceDate); + + if (!innerResult.getSuccess()) { + innerResult = this.parseImplicitDate(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseWeekdayOfMonth(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseDurationWithAgoAndLater(er.getText(), referenceDate); + } + + // NumberWithMonth must be the second last one, because it only need to find a number and a month to get a "success" + if (!innerResult.getSuccess()) { + innerResult = this.parseNumberWithMonth(er.getText(), referenceDate); + } + + // SingleNumber last one + if (!innerResult.getSuccess()) { + innerResult = this.parseSingleNumber(er.getText(), referenceDate); + } + + if (innerResult.getSuccess()) { + ImmutableMap.Builder futureResolution = ImmutableMap.builder(); + futureResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())); + + innerResult.setFutureResolution(futureResolution.build()); + + ImmutableMap.Builder pastResolution = ImmutableMap.builder(); + pastResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())); + + innerResult.setPastResolution(pastResolution.build()); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + // parse basic patterns in DateRegexList + private DateTimeResolutionResult parseBasicRegexMatch(String text, LocalDateTime referenceDate) { + String trimmedText = text.trim(); + + for (Pattern regex : this.config.getDateRegexes()) { + int offset = 0; + String relativeStr = null; + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, trimmedText)).findFirst(); + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(regex, this.config.getDateTokenPrefix() + trimmedText)).findFirst(); + // Handing cases like "(this)? 5.12" which only be recognized in "on (this)? 5.12" + if (match.isPresent()) { + offset = this.config.getDateTokenPrefix().length(); + relativeStr = match.get().getGroup("order").value.toLowerCase(); + } + } + + if (match.isPresent()) { + ConditionalMatch relativeRegex = RegexExtension.matchEnd(config.getStrictRelativeRegex(), text.substring(0, match.get().index), true); + boolean isContainRelative = relativeRegex.getSuccess() && match.get().index + match.get().length == trimmedText.length(); + if ((match.get().index == offset && match.get().length == trimmedText.length()) || isContainRelative) { + // Handing cases which contain relative term like "this 5/12" + if (match.get().index != offset) { + relativeStr = relativeRegex.getMatch().get().value; + } + + // LUIS value string will be set in Match2Date method + DateTimeResolutionResult ret = this.match2Date(match, referenceDate, relativeStr); + return ret; + } + } + } + + return new DateTimeResolutionResult(); + } + + // match several other cases + // including 'today', 'the day after tomorrow', 'on 13' + private DateTimeResolutionResult parseImplicitDate(String text, LocalDateTime referenceDate) { + String trimmedText = text.trim(); + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + // handle "on 12" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getOnRegex(), this.config.getDateTokenPrefix() + trimmedText)).findFirst(); + if (match.isPresent() && match.get().index == 3 && match.get().length == trimmedText.length()) { + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + String dayStr = match.get().getGroup("day").value.toLowerCase(); + int day = this.config.getDayOfMonth().get(dayStr); + + ret.setTimex(DateTimeFormatUtil.luisDate(-1, -1, day)); + + LocalDateTime futureDate; + LocalDateTime pastDate; + String tryStr = DateTimeFormatUtil.luisDate(year, month, day); + if (DateUtil.tryParse(tryStr) != null) { + futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + + if (futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusMonths(1); + } + + if (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate)) { + pastDate = pastDate.minusMonths(1); + } + } else { + futureDate = DateUtil.safeCreateFromMinValue(year, month + 1, day); + pastDate = DateUtil.safeCreateFromMinValue(year, month - 1, day); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // handle "today", "the day before yesterday" + ConditionalMatch exactMatch = RegexExtension.matchExact(this.config.getSpecialDayRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + int swift = getSwiftDay(exactMatch.getMatch().get().value); + + LocalDateTime value = referenceDate.toLocalDate().atStartOfDay().plusDays(swift); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "two days from tomorrow" + exactMatch = RegexExtension.matchExact(this.config.getSpecialDayWithNumRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + + int swift = getSwiftDay(exactMatch.getMatch().get().getGroup("day").value); + List numErs = this.config.getIntegerExtractor().extract(trimmedText); + Object numberParsed = this.config.getNumberParser().parse(numErs.get(0)).getValue(); + int numOfDays = Math.round(((Double)numberParsed).floatValue()); + + LocalDateTime value = referenceDate.plusDays(numOfDays + swift); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "two sundays from now" + exactMatch = RegexExtension.matchExact(this.config.getRelativeWeekDayRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + List numErs = this.config.getIntegerExtractor().extract(trimmedText); + Object numberParsed = this.config.getNumberParser().parse(numErs.get(0)).getValue(); + int num = Math.round(((Double)numberParsed).floatValue()); + + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = referenceDate; + + // Check whether the determined day of this week has passed. + if (value.getDayOfWeek().getValue() > this.config.getDayOfWeek().get(weekdayStr)) { + num--; + } + + while (num-- > 0) { + value = DateUtil.next(value, this.config.getDayOfWeek().get(weekdayStr)); + } + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "next Sunday" + exactMatch = RegexExtension.matchExact(this.config.getNextRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = DateUtil.next(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "this Friday" + exactMatch = RegexExtension.matchExact(this.config.getThisRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = DateUtil.thisDate(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "last Friday", "last mon" + exactMatch = RegexExtension.matchExact(this.config.getLastRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = DateUtil.last(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "Friday" + exactMatch = RegexExtension.matchExact(this.config.getWeekDayRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + int weekDay = this.config.getDayOfWeek().get(weekdayStr); + LocalDateTime value = DateUtil.thisDate(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + if (weekDay == 0) { + weekDay = 7; + } + + if (weekDay < referenceDate.getDayOfWeek().getValue()) { + value = DateUtil.next(referenceDate, weekDay); + } + + ret.setTimex("XXXX-WXX-" + weekDay); + LocalDateTime futureDate = value; + LocalDateTime pastDate = value; + if (futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusDays(7); + } + + if (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate)) { + pastDate = pastDate.minusDays(7); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // handle "for the 27th." + match = Arrays.stream(RegExpUtility.getMatches(this.config.getForTheRegex(), text)).findFirst(); + if (match.isPresent()) { + int day; + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + String dayStr = match.get().getGroup("DayOfMonth").value.toLowerCase(); + + int start = match.get().getGroup("DayOfMonth").index; + int length = match.get().getGroup("DayOfMonth").length; + + // create a extract comments which content ordinal string of text + ExtractResult er = new ExtractResult(start, length, dayStr, null, null); + + Object numberParsed = this.config.getNumberParser().parse(er).getValue(); + day = Math.round(((Double)numberParsed).floatValue()); + + ret.setTimex(DateTimeFormatUtil.luisDate(-1, -1, day)); + + LocalDateTime futureDate; + String tryStr = DateTimeFormatUtil.luisDate(year, month, day); + + if (DateUtil.tryParse(tryStr) != null) { + futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + } else { + futureDate = DateUtil.safeCreateFromMinValue(year, month + 1, day); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(ret.getFutureValue()); + ret.setSuccess(true); + + return ret; + } + + // handling cases like 'Thursday the 21st', which both 'Thursday' and '21st' refer to a same date + match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekDayAndDayOfMonthRegex(), text)).findFirst(); + if (match.isPresent()) { + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + String dayStr = match.get().getGroup("DayOfMonth").value.toLowerCase(); + + int start = match.get().getGroup("DayOfMonth").index; + int length = match.get().getGroup("DayOfMonth").length; + + // create a extract comments which content ordinal string of text + ExtractResult erTmp = new ExtractResult(start, length, dayStr, null, null); + + Object numberParsed = this.config.getNumberParser().parse(erTmp).getValue(); + int day = Math.round(((Double)numberParsed).floatValue()); + + // the validity of the phrase is guaranteed in the Date Extractor + ret.setTimex(DateTimeFormatUtil.luisDate(year, month, day)); + ret.setFutureValue(LocalDateTime.of(year, month, day, 0, 0)); + ret.setPastValue(LocalDateTime.of(year, month, day, 0, 0)); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private DateTimeResolutionResult parseWeekdayOfMonth(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekDayOfMonthRegex(), this.config.getDateTokenPrefix() + trimmedText)).findFirst(); + if (!match.isPresent()) { + return ret; + } + + String cardinalStr = match.get().getGroup("cardinal").value; + String weekdayStr = match.get().getGroup("weekday").value; + String monthStr = match.get().getGroup("month").value; + Boolean noYear = false; + int year; + + int cardinal = this.config.isCardinalLast(cardinalStr) ? 5 : this.config.getCardinalMap().get(cardinalStr); + + int weekday = this.config.getDayOfWeek().get(weekdayStr); + int month; + if (StringUtility.isNullOrEmpty(monthStr)) { + int swift = this.config.getSwiftMonthOrYear(trimmedText); + + month = referenceDate.plusMonths(swift).getMonthValue(); + year = referenceDate.plusMonths(swift).getYear(); + } else { + month = this.config.getMonthOfYear().get(monthStr); + year = referenceDate.getYear(); + noYear = true; + } + + LocalDateTime value = computeDate(cardinal, weekday, month, year); + if (value.getMonthValue() != month) { + cardinal -= 1; + value = value.minusDays(7); + } + + LocalDateTime futureDate = value; + LocalDateTime pastDate = value; + if (noYear && futureDate.isBefore(referenceDate)) { + futureDate = computeDate(cardinal, weekday, month, year + 1); + if (futureDate.getMonthValue() != month) { + futureDate = futureDate.minusDays(7); + } + } + + if (noYear && (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate))) { + pastDate = computeDate(cardinal, weekday, month, year - 1); + if (pastDate.getMonthValue() != month) { + pastDate = pastDate.minusDays(7); + } + } + + // here is a very special case, timeX followe future date + ret.setTimex("XXXX-" + String.format("%02d", month) + "-WXX-" + weekday + "-#" + cardinal); + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // Handle cases like "two days ago" + private DateTimeResolutionResult parseDurationWithAgoAndLater(String text, LocalDateTime referenceDate) { + + return AgoLaterUtil.parseDurationWithAgoAndLater(text, referenceDate, + config.getDurationExtractor(), config.getDurationParser(), config.getUnitMap(), config.getUnitRegex(), + config.getUtilityConfiguration(), this::getSwiftDay); + } + + // handle cases like "January first", "twenty-two of August" + // handle cases like "20th of next month" + private DateTimeResolutionResult parseNumberWithMonth(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + int month = 0; + int day = 0; + int year = referenceDate.getYear(); + Boolean ambiguous = true; + + List er = this.config.getOrdinalExtractor().extract(trimmedText); + if (er.size() == 0) { + er = this.config.getIntegerExtractor().extract(trimmedText); + } + + if (er.size() == 0) { + return ret; + } + + Object numberParsed = this.config.getNumberParser().parse(er.get(0)).getValue(); + int num = Math.round(((Double)numberParsed).floatValue()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getMonthRegex(), trimmedText)).findFirst(); + if (match.isPresent()) { + month = this.config.getMonthOfYear().get(match.get().value.trim()); + day = num; + + String suffix = trimmedText.substring((er.get(0).getStart() + er.get(0).getLength())); + + Optional matchYear = Arrays.stream(RegExpUtility.getMatches(this.config.getYearSuffix(), suffix)).findFirst(); + if (matchYear.isPresent()) { + year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(matchYear.get()); + if (year != Constants.InvalidYear) { + ambiguous = false; + } + } + } + + // handling relatived month + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeMonthRegex(), trimmedText)).findFirst(); + if (match.isPresent()) { + String monthStr = match.get().getGroup("order").value; + int swift = this.config.getSwiftMonthOrYear(monthStr); + month = referenceDate.plusMonths(swift).getMonthValue(); + year = referenceDate.plusMonths(swift).getYear(); + day = num; + ambiguous = false; + } + } + + // handling casesd like 'second Sunday' + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekDayRegex(), trimmedText)).findFirst(); + if (match.isPresent()) { + month = referenceDate.getMonthValue(); + // resolve the date of wanted week day + int wantedWeekDay = this.config.getDayOfWeek().get(match.get().getGroup("weekday").value); + LocalDateTime firstDate = DateUtil.safeCreateFromMinValue(referenceDate.getYear(), referenceDate.getMonthValue(), 1); + int firstWeekDay = firstDate.getDayOfWeek().getValue(); + LocalDateTime firstWantedWeekDay = firstDate.plusDays(wantedWeekDay > firstWeekDay ? wantedWeekDay - firstWeekDay : wantedWeekDay - firstWeekDay + 7); + int answerDay = firstWantedWeekDay.getDayOfMonth() + (num - 1) * 7; + day = answerDay; + ambiguous = false; + } + } + + if (!match.isPresent()) { + return ret; + } + + // for LUIS format value string + LocalDateTime futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + LocalDateTime pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + + if (ambiguous) { + ret.setTimex(DateTimeFormatUtil.luisDate(-1, month, day)); + if (futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusYears(1); + } + + if (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate)) { + pastDate = pastDate.minusYears(1); + } + } else { + ret.setTimex(DateTimeFormatUtil.luisDate(year, month, day)); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // handle cases like "the 27th". In the extractor, only the unmatched weekday and date will output this date. + private DateTimeResolutionResult parseSingleNumber(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + int day = 0; + + List er = this.config.getOrdinalExtractor().extract(trimmedText); + if (er.size() == 0) { + er = this.config.getIntegerExtractor().extract(trimmedText); + } + + if (er.size() == 0) { + return ret; + } + + Object numberParsed = this.config.getNumberParser().parse(er.get(0)).getValue(); + day = Math.round(((Double)numberParsed).floatValue()); + + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + + // for LUIS format value string + ret.setTimex(DateTimeFormatUtil.luisDate(-1, -1, day)); + LocalDateTime pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + LocalDateTime futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + + if (!futureDate.isEqual(LocalDateTime.MIN) && futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusMonths(1); + } + + if (!pastDate.isEqual(LocalDateTime.MIN) && (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate))) { + pastDate = pastDate.minusMonths(1); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + private static LocalDateTime computeDate(int cardinal, int weekday, int month, int year) { + LocalDateTime firstDay = DateUtil.safeCreateFromMinValue(year, month, 1); + LocalDateTime firstWeekday = DateUtil.thisDate(firstDay, weekday); + int dayOfWeekOfFirstDay = firstDay.getDayOfWeek().getValue(); + + if (weekday == 0) { + weekday = 7; + } + + if (dayOfWeekOfFirstDay == 0) { + dayOfWeekOfFirstDay = 7; + } + + if (weekday < dayOfWeekOfFirstDay) { + firstWeekday = DateUtil.next(firstDay, weekday); + } + + return firstWeekday.plusDays(7 * (cardinal - 1)); + } + + // parse a regex match which includes 'day', 'month' and 'year' (optional) group + private DateTimeResolutionResult match2Date(Optional match, LocalDateTime referenceDate, String relativeStr) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int month = 0; + int day = 0; + int year = 0; + + String monthStr = match.get().getGroup("month").value.toLowerCase(); + String dayStr = match.get().getGroup("day").value.toLowerCase(); + String weekdayStr = match.get().getGroup("weekday").value.toLowerCase(); + String yearStr = match.get().getGroup("year").value.toLowerCase(); + String writtenYearStr = match.get().getGroup("fullyear").value.toLowerCase(); + + if (this.config.getMonthOfYear().containsKey(monthStr) && this.config.getDayOfMonth().containsKey(dayStr)) { + + month = this.config.getMonthOfYear().get(monthStr); + day = this.config.getDayOfMonth().get(dayStr); + + if (!StringUtility.isNullOrEmpty(yearStr)) { + + year = this.config.getDateExtractor().getYearFromText(match.get()); + + } else if (!StringUtility.isNullOrEmpty(yearStr)) { + + year = Integer.parseInt(yearStr); + if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { + year += 1900; + } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { + year += 2000; + } + } + } + + Boolean noYear = false; + if (year == 0) { + year = referenceDate.getYear(); + if (!StringUtility.isNullOrEmpty(relativeStr)) { + int swift = config.getSwiftMonthOrYear(relativeStr); + + // @TODO Improve handling of next/last in particular cases "next friday 5/12" when the next friday is not 5/12. + if (!StringUtility.isNullOrEmpty(weekdayStr)) { + swift = 0; + } + year += swift; + } else { + noYear = true; + } + + ret.setTimex(DateTimeFormatUtil.luisDate(-1, month, day)); + } else { + ret.setTimex(DateTimeFormatUtil.luisDate(year, month, day)); + } + + HashMap futurePastDates = DateContext.generateDates(noYear, referenceDate, year, month, day); + LocalDateTime futureDate = futurePastDates.get(Constants.FutureDate); + LocalDateTime pastDate = futurePastDates.get(Constants.PastDate); + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + private int getSwiftDay(String text) { + String trimmedText = this.config.normalize(text.trim().toLowerCase()); + int swift = 0; + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeDayRegex(), text)).findFirst(); + + // The sequence here is important + // As suffix "day before yesterday" should be matched before suffix "day before" or "yesterday" + if (config.getSameDayTerms().contains(trimmedText)) { + swift = 0; + } else if (endsWithTerms(trimmedText, config.getPlusTwoDayTerms())) { + swift = 2; + } else if (endsWithTerms(trimmedText, config.getMinusTwoDayTerms())) { + swift = -2; + } else if (endsWithTerms(trimmedText, config.getPlusOneDayTerms())) { + swift = 1; + } else if (endsWithTerms(trimmedText, config.getMinusOneDayTerms())) { + swift = -1; + } else if (match.isPresent()) { + swift = getSwift(text); + } + + return swift; + } + + private int getSwift(String text) { + String trimmedText = text.trim().toLowerCase(); + + int swift = 0; + if (RegExpUtility.getMatches(this.config.getNextPrefixRegex(), trimmedText).length > 0) { + swift = 1; + } else if (RegExpUtility.getMatches(this.config.getPastPrefixRegex(), trimmedText).length > 0) { + swift = -1; + } + + return swift; + } + + private boolean endsWithTerms(String text, List terms) { + boolean result = false; + + for (String term : terms) { + if (text.endsWith(term)) { + result = true; + break; + } + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java new file mode 100644 index 000000000..f3d5c94a3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java @@ -0,0 +1,1897 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateContext; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.DurationParsingUtil; +import com.microsoft.recognizers.text.datetime.utilities.GetModAndDateResult; +import com.microsoft.recognizers.text.datetime.utilities.NthBusinessDayResult; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.IntegerUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.sql.Time; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +public class BaseDatePeriodParser implements IDateTimeParser { + + private static final String parserName = Constants.SYS_DATETIME_DATEPERIOD; //"DatePeriod"; + private static boolean inclusiveEndPeriod = false; + + private final IDatePeriodParserConfiguration config; + + public BaseDatePeriodParser(IDatePeriodParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime refDate) { + + DateTimeResolutionResult value = null; + if (er.getType().equals(parserName)) { + DateTimeResolutionResult innerResult = parseBaseDatePeriod(er.getText(), refDate); + + if (!innerResult.getSuccess()) { + innerResult = parseComplexDatePeriod(er.getText(), refDate); + } + + if (innerResult.getSuccess()) { + if (innerResult.getMod() != null && innerResult.getMod().equals(Constants.BEFORE_MOD)) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())) + .build()); + } else if (innerResult.getMod() != null && innerResult.getMod().equals(Constants.AFTER_MOD)) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())) + .build()); + } else if (innerResult.getFutureValue() != null && innerResult.getPastValue() != null) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getFutureValue()).getValue0())) + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getFutureValue()).getValue1())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getPastValue()).getValue0())) + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getPastValue()).getValue1())) + .build()); + } else { + innerResult.setFutureResolution(new HashMap<>()); + innerResult.setPastResolution(new HashMap<>()); + } + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), value, "", "", er.getMetadata()); + + if (value != null) { + ret.setTimexStr(value.getTimex()); + } + + return ret; + } + + // Process case like "from|between START to|and END" where START/END can be dateRange or datePoint + private DateTimeResolutionResult parseComplexDatePeriod(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getComplexDatePeriodRegex(), text)).findFirst(); + + if (match.isPresent()) { + LocalDateTime futureBegin = LocalDateTime.MIN; + LocalDateTime futureEnd = LocalDateTime.MIN; + LocalDateTime pastBegin = LocalDateTime.MIN; + LocalDateTime pastEnd = LocalDateTime.MIN; + boolean isSpecificDate = false; + boolean isStartByWeek = false; + boolean isEndByWeek = false; + DateContext dateContext = getYearContext(match.get().getGroup("start").value.trim(), match.get().getGroup("end").value.trim(), text); + + DateTimeResolutionResult startResolution = parseSingleTimePoint(match.get().getGroup("start").value.trim(), referenceDate, dateContext); + + if (startResolution.getSuccess()) { + futureBegin = (LocalDateTime)startResolution.getFutureValue(); + pastBegin = (LocalDateTime)startResolution.getPastValue(); + isSpecificDate = true; + } else { + startResolution = parseBaseDatePeriod(match.get().getGroup("start").value.trim(), referenceDate, dateContext); + if (startResolution.getSuccess()) { + futureBegin = ((Pair)startResolution.getFutureValue()).getValue0(); + pastBegin = ((Pair)startResolution.getPastValue()).getValue0(); + + if (startResolution.getTimex().contains("-W")) { + isStartByWeek = true; + } + } + } + + if (startResolution.getSuccess()) { + DateTimeResolutionResult endResolution = parseSingleTimePoint(match.get().getGroup("end").value.trim(), referenceDate, dateContext); + + if (endResolution.getSuccess()) { + futureEnd = (LocalDateTime)endResolution.getFutureValue(); + pastEnd = (LocalDateTime)endResolution.getPastValue(); + isSpecificDate = true; + } else { + endResolution = parseBaseDatePeriod(match.get().getGroup("end").value.trim(), referenceDate, dateContext); + + if (endResolution.getSuccess()) { + futureEnd = ((Pair)endResolution.getFutureValue()).getValue0(); + pastEnd = ((Pair)endResolution.getPastValue()).getValue0(); + + if (endResolution.getTimex().contains("-W")) { + isEndByWeek = true; + } + } + } + + if (endResolution.getSuccess()) { + if (futureBegin.isAfter(futureEnd)) { + if (dateContext == null || dateContext.isEmpty()) { + futureBegin = pastBegin; + } else { + futureBegin = dateContext.swiftDateObject(futureBegin, futureEnd); + } + } + + if (pastEnd.isBefore(pastBegin)) { + if (dateContext == null || dateContext.isEmpty()) { + pastEnd = futureEnd; + } else { + pastBegin = dateContext.swiftDateObject(pastBegin, pastEnd); + } + } + + // If both begin/end are date ranges in "Month", the Timex should be ByMonth + // The year period case should already be handled in Basic Cases + DatePeriodTimexType datePeriodTimexType = DatePeriodTimexType.ByMonth; + + if (isSpecificDate) { + // If at least one of the begin/end is specific date, the Timex should be ByDay + datePeriodTimexType = DatePeriodTimexType.ByDay; + } else if (isStartByWeek && isEndByWeek) { + // If both begin/end are date ranges in "Week", the Timex should be ByWeek + datePeriodTimexType = DatePeriodTimexType.ByWeek; + } + + ret.setTimex(TimexUtility.generateDatePeriodTimex(futureBegin, futureEnd, datePeriodTimexType, pastBegin, pastEnd)); + + ret.setFutureValue(new Pair<>(futureBegin, futureEnd)); + ret.setPastValue(new Pair<>(pastBegin, pastEnd)); + ret.setSuccess(true); + } + } + } + return ret; + } + + private DateTimeResolutionResult parseBaseDatePeriod(String text, LocalDateTime referenceDate) { + return parseBaseDatePeriod(text, referenceDate, null); + } + + private DateTimeResolutionResult parseBaseDatePeriod(String text, LocalDateTime referenceDate, DateContext dateContext) { + DateTimeResolutionResult innerResult = parseMonthWithYear(text, referenceDate); + + if (!innerResult.getSuccess()) { + innerResult = parseSimpleCases(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseOneWordPeriod(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = mergeTwoTimePoints(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseYear(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWeekOfMonth(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWeekOfYear(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseHalfYear(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseQuarter(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseSeason(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWhichWeek(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWeekOfDate(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseMonthOfDate(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseDecade(text, referenceDate); + } + + // Cases like "within/less than/more than x weeks from/before/after today" + if (!innerResult.getSuccess()) { + innerResult = parseDatePointWithAgoAndLater(text, referenceDate); + } + + // Parse duration should be at the end since it will extract "the last week" from "the last week of July" + if (!innerResult.getSuccess()) { + innerResult = parseDuration(text, referenceDate); + } + + // Cases like "21st century" + if (!innerResult.getSuccess()) { + innerResult = parseOrdinalNumberWithCenturySuffix(text, referenceDate); + } + + if (innerResult.getSuccess() && dateContext != null) { + innerResult = dateContext.processDatePeriodEntityResolution(innerResult); + } + + return innerResult; + } + + private DateTimeResolutionResult parseOrdinalNumberWithCenturySuffix(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional er = this.config.getOrdinalExtractor().extract(text).stream().findFirst(); + + if (er.isPresent() && er.get().getStart() + er.get().getLength() < text.length()) { + String afterString = text.substring(er.get().getStart() + er.get().getLength()).trim(); + + // It falls into the cases like "21st century" + if (Arrays.stream(RegExpUtility.getMatches(this.config.getCenturySuffixRegex(), afterString)).findFirst().isPresent()) { + ParseResult number = this.config.getNumberParser().parse(er.get()); + + if (number.getValue() != null) { + // Note that 1st century means from year 0 - 100 + int startYear = (Math.round(((Double)number.getValue()).floatValue()) - 1) * Constants.CenturyYearsCount; + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(startYear, 1, 1); + LocalDateTime endDate = DateUtil.safeCreateFromMinValue(startYear + Constants.CenturyYearsCount, 1, 1); + + String startLuisStr = DateTimeFormatUtil.luisDate(startDate); + String endLuisStr = DateTimeFormatUtil.luisDate(endDate); + String durationTimex = "P" + Constants.CenturyYearsCount + "Y"; + + ret.setTimex(String.format("(%s,%s,%s)", startLuisStr, endLuisStr, durationTimex)); + ret.setFutureValue(new Pair<>(startDate, endDate)); + ret.setPastValue(new Pair<>(startDate, endDate)); + ret.setSuccess(true); + } + } + } + + return ret; + } + + private DateTimeResolutionResult parseDatePointWithAgoAndLater(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional er = this.config.getDateExtractor().extract(text, referenceDate).stream().findFirst(); + + if (er.isPresent()) { + String beforeString = text.substring(0, er.get().getStart()); + boolean isAgo = Arrays.stream(RegExpUtility.getMatches(this.config.getAgoRegex(), er.get().getText())).findFirst().isPresent(); + boolean isLater = Arrays.stream(RegExpUtility.getMatches(this.config.getLaterRegex(), er.get().getText())).findFirst().isPresent(); + + if (!StringUtility.isNullOrEmpty(beforeString) && (isAgo || isLater)) { + boolean isLessThanOrWithIn = false; + boolean isMoreThan = false; + + // cases like "within 3 days from yesterday/tomorrow" does not make any sense + if (er.get().getText().contains("today") || er.get().getText().contains("now")) { + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWithinNextPrefixRegex(), beforeString)).findFirst(); + if (match.isPresent()) { + boolean isNext = !StringUtility.isNullOrEmpty(match.get().getGroup("next").value); + + // cases like "within the next 5 days before today" is not acceptable + if (!(isNext && isAgo)) { + isLessThanOrWithIn = true; + } + } + } + + isLessThanOrWithIn = isLessThanOrWithIn || (Arrays.stream(RegExpUtility.getMatches(this.config.getLessThanRegex(), beforeString)).findFirst().isPresent()); + isMoreThan = Arrays.stream(RegExpUtility.getMatches(this.config.getMoreThanRegex(), beforeString)).findFirst().isPresent(); + + DateTimeParseResult pr = this.config.getDateParser().parse(er.get(), referenceDate); + Optional durationExtractionResult = this.config.getDurationExtractor().extract(er.get().getText()).stream().findFirst(); + + if (durationExtractionResult.isPresent()) { + ParseResult duration = this.config.getDurationParser().parse(durationExtractionResult.get()); + long durationInSeconds = Math.round((Double)((DateTimeResolutionResult)(duration.getValue())).getPastValue()); + + if (isLessThanOrWithIn) { + LocalDateTime startDate; + LocalDateTime endDate; + + if (isAgo) { + startDate = (LocalDateTime)((DateTimeResolutionResult)(pr.getValue())).getPastValue(); + endDate = startDate.plusSeconds(durationInSeconds); + } else { + endDate = (LocalDateTime)((DateTimeResolutionResult)(pr.getValue())).getFutureValue(); + startDate = endDate.minusSeconds(durationInSeconds); + } + + if (startDate != LocalDateTime.MIN) { + String startLuisStr = DateTimeFormatUtil.luisDate(startDate); + String endLuisStr = DateTimeFormatUtil.luisDate(endDate); + String durationTimex = ((DateTimeResolutionResult)(duration.getValue())).getTimex(); + + ret.setTimex(String.format("(%s,%s,%s)", startLuisStr, endLuisStr, durationTimex)); + ret.setFutureValue(new Pair<>(startDate, endDate)); + ret.setPastValue(new Pair<>(startDate, endDate)); + ret.setSuccess(true); + } + } else if (isMoreThan) { + ret.setMod(isAgo ? Constants.BEFORE_MOD : Constants.AFTER_MOD); + + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue(((DateTimeResolutionResult)(pr.getValue())).getFutureValue()); + ret.setPastValue(((DateTimeResolutionResult)(pr.getValue())).getPastValue()); + ret.setSuccess(true); + } + } + } + } + + return ret; + } + + private DateTimeResolutionResult parseSingleTimePoint(String text, LocalDateTime referenceDate, DateContext dateContext) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ExtractResult er = this.config.getDateExtractor().extract(text, referenceDate).stream().findFirst().orElse(null); + + if (er != null) { + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekWithWeekDayRangeRegex(), text)).findFirst(); + String weekPrefix = null; + if (match.isPresent()) { + weekPrefix = match.get().getGroup("week").value; + } + + if (!StringUtility.isNullOrEmpty(weekPrefix)) { + er.setText(weekPrefix + " " + er.getText()); + } + + ParseResult pr = this.config.getDateParser().parse(er, referenceDate); + + if (pr != null) { + ret.setTimex("(" + ((DateTimeParseResult)pr).getTimexStr()); + ret.setFutureValue(((DateTimeResolutionResult)pr.getValue()).getFutureValue()); + ret.setPastValue(((DateTimeResolutionResult)pr.getValue()).getPastValue()); + ret.setSuccess(true); + } + + if (dateContext != null) { + ret = dateContext.processDateEntityResolution(ret); + } + } + + return ret; + } + + private DateTimeResolutionResult parseSimpleCases(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int year = referenceDate.getYear(); + int month = referenceDate.getMonthValue(); + int beginDay; + int endDay; + boolean noYear = true; + + ConditionalMatch match = RegexExtension.matchExact(this.config.getMonthFrontBetweenRegex(), text, true); + String beginLuisStr; + String endLuisStr; + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getBetweenRegex(), text, true); + } + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getMonthFrontSimpleCasesRegex(), text, true); + } + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getSimpleCasesRegex(), text, true); + } + + if (match.getSuccess()) { + MatchGroup days = match.getMatch().get().getGroup("day"); + beginDay = this.config.getDayOfMonth().get(days.captures[0].value.toLowerCase()); + endDay = this.config.getDayOfMonth().get(days.captures[1].value.toLowerCase()); + + // parse year + year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + if (year != Constants.InvalidYear) { + noYear = false; + } else { + year = referenceDate.getYear(); + } + + String monthStr = match.getMatch().get().getGroup("month").value; + if (!StringUtility.isNullOrEmpty(monthStr)) { + month = this.config.getMonthOfYear().get(monthStr.toLowerCase()); + } else { + monthStr = match.getMatch().get().getGroup("relmonth").value.trim().toLowerCase(); + int swiftMonth = this.config.getSwiftDayOrMonth(monthStr); + switch (swiftMonth) { + case 1: + if (month != 12) { + month += 1; + } else { + month = 1; + year += 1; + } + break; + case -1: + if (month != 1) { + month -= 1; + } else { + month = 12; + year -= 1; + } + break; + default: + break; + } + + if (this.config.isFuture(monthStr)) { + noYear = false; + } + } + } else { + return ret; + } + + if (noYear) { + beginLuisStr = DateTimeFormatUtil.luisDate(-1, month, beginDay); + endLuisStr = DateTimeFormatUtil.luisDate(-1, month, endDay); + } else { + beginLuisStr = DateTimeFormatUtil.luisDate(year, month, beginDay); + endLuisStr = DateTimeFormatUtil.luisDate(year, month, endDay); + } + + int futureYear = year; + int pastYear = year; + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(year, month, beginDay); + + if (noYear && startDate.isBefore(referenceDate)) { + futureYear++; + } + + if (noYear && (startDate.isAfter(referenceDate) || startDate.isEqual(referenceDate))) { + pastYear--; + } + + HashMap futurePastBeginDates = DateContext.generateDates(noYear, referenceDate, year, month, beginDay); + HashMap futurePastEndDates = DateContext.generateDates(noYear, referenceDate, year, month, endDay); + + ret.setTimex(String.format("(%s,%s,P%sD)", beginLuisStr, endLuisStr, (endDay - beginDay))); + ret.setFutureValue(new Pair<>(futurePastBeginDates.get(Constants.FutureDate), + futurePastEndDates.get(Constants.FutureDate))); + ret.setPastValue(new Pair<>(futurePastBeginDates.get(Constants.PastDate), + futurePastEndDates.get(Constants.PastDate))); + ret.setSuccess(true); + + return ret; + } + + private boolean isPresent(int swift) { + return swift == 0; + } + + private DateTimeResolutionResult parseOneWordPeriod(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int year = referenceDate.getYear(); + int month = referenceDate.getMonthValue(); + int futureYear = year; + int pastYear = year; + boolean earlyPrefix = false; + boolean latePrefix = false; + boolean midPrefix = false; + boolean isRef = false; + + boolean earlierPrefix = false; + boolean laterPrefix = false; + + String trimmedText = text.trim().toLowerCase(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getOneWordPeriodRegex(), trimmedText, true); + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getLaterEarlyPeriodRegex(), trimmedText, true); + } + + // For cases "that week|month|year" + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getReferenceDatePeriodRegex(), trimmedText, true); + isRef = true; + ret.setMod(Constants.REF_UNDEF_MOD); + } + + if (match.getSuccess()) { + if (!match.getMatch().get().getGroup("EarlyPrefix").value.equals("")) { + earlyPrefix = true; + trimmedText = match.getMatch().get().getGroup(Constants.SuffixGroupName).value; + ret.setMod(Constants.EARLY_MOD); + } else if (!match.getMatch().get().getGroup("LatePrefix").value.equals("")) { + latePrefix = true; + trimmedText = match.getMatch().get().getGroup(Constants.SuffixGroupName).value; + ret.setMod(Constants.LATE_MOD); + } else if (!match.getMatch().get().getGroup("MidPrefix").value.equals("")) { + midPrefix = true; + trimmedText = match.getMatch().get().getGroup(Constants.SuffixGroupName).value; + ret.setMod(Constants.MID_MOD); + } + + int swift = 0; + String monthStr = match.getMatch().get().getGroup("month").value; + if (!StringUtility.isNullOrEmpty(monthStr)) { + swift = this.config.getSwiftYear(trimmedText); + } else { + swift = this.config.getSwiftDayOrMonth(trimmedText); + } + + // Handle the abbreviation of DatePeriod, e.g., 'eoy(end of year)', the behavior of 'eoy' should be the same as 'end of year' + Optional unspecificEndOfRangeMatch = Arrays.stream(RegExpUtility.getMatches(config.getUnspecificEndOfRangeRegex(), match.getMatch().get().value)).findFirst(); + if (unspecificEndOfRangeMatch.isPresent()) { + latePrefix = true; + trimmedText = match.getMatch().get().value; + ret.setMod(Constants.LATE_MOD); + } + + if (!match.getMatch().get().getGroup("RelEarly").value.equals("")) { + earlierPrefix = true; + if (isPresent(swift)) { + ret.setMod(null); + } + } else if (!match.getMatch().get().getGroup("RelLate").value.equals("")) { + laterPrefix = true; + if (isPresent(swift)) { + ret.setMod(null); + } + } + + if (this.config.isYearToDate(trimmedText)) { + ret.setTimex(String.format("%04d", referenceDate.getYear())); + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), 1, 1), referenceDate)); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), 1, 1), referenceDate)); + + ret.setSuccess(true); + return ret; + } + + if (this.config.isMonthToDate(trimmedText)) { + ret.setTimex(String.format("%04d-%02d", referenceDate.getYear(), referenceDate.getMonthValue())); + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), referenceDate.getMonthValue(), 1), + referenceDate)); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), referenceDate.getMonthValue(), 1), + referenceDate)); + + ret.setSuccess(true); + return ret; + } + + if (!StringUtility.isNullOrEmpty(monthStr)) { + swift = this.config.getSwiftYear(trimmedText); + + month = this.config.getMonthOfYear().get(monthStr.toLowerCase()); + + if (swift >= -1) { + ret.setTimex(String.format("%04d-%02d", referenceDate.getYear() + swift, month)); + year = year + swift; + futureYear = pastYear = year; + } else { + ret.setTimex(String.format("XXXX-%02d", month)); + if (month < referenceDate.getMonthValue()) { + futureYear++; + } + + if (month >= referenceDate.getMonthValue()) { + pastYear--; + } + } + } else { + swift = this.config.getSwiftDayOrMonth(trimmedText); + + if (this.config.isWeekOnly(trimmedText)) { + LocalDateTime thursday = DateUtil.thisDate(referenceDate, DayOfWeek.THURSDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + ret.setTimex(isRef ? TimexUtility.generateWeekTimex() : TimexUtility.generateWeekTimex(thursday)); + + LocalDateTime beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.MONDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + LocalDateTime endValue = DateUtil.thisDate(referenceDate, DayOfWeek.SUNDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + LocalDateTime endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + + if (earlyPrefix) { + endValue = DateUtil.thisDate(referenceDate, DayOfWeek.WEDNESDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (midPrefix) { + beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.TUESDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + endValue = DateUtil.thisDate(referenceDate, DayOfWeek.FRIDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (latePrefix) { + beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.THURSDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + } + + if (earlierPrefix && swift == 0) { + if (endDate.isAfter(referenceDate)) { + endDate = referenceDate; + } + } else if (laterPrefix && swift == 0) { + if (beginDate.isBefore(referenceDate)) { + beginDate = referenceDate; + } + } + + if (latePrefix && swift != 0) { + ret.setMod(Constants.LATE_MOD); + } + + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + + ret.setSuccess(true); + return ret; + } + + if (this.config.isWeekend(trimmedText)) { + LocalDateTime beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.SATURDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + LocalDateTime endValue = DateUtil.thisDate(referenceDate, DayOfWeek.SUNDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + ret.setTimex(isRef ? TimexUtility.generateWeekendTimex() : TimexUtility.generateWeekendTimex(beginDate)); + + LocalDateTime endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + + ret.setSuccess(true); + return ret; + } + + if (this.config.isMonthOnly(trimmedText)) { + LocalDateTime date = referenceDate.plusMonths(swift); + month = date.getMonthValue(); + year = date.getYear(); + + ret.setTimex(isRef ? TimexUtility.generateMonthTimex() : TimexUtility.generateMonthTimex(date)); + + futureYear = pastYear = year; + } else if (this.config.isYearOnly(trimmedText)) { + LocalDateTime date = referenceDate.plusYears(swift); + year = date.getYear(); + + if (!StringUtility.isNullOrEmpty(match.getMatch().get().getGroup("special").value)) { + String specialYearPrefixes = this.config.getSpecialYearPrefixesMap().get(match.getMatch().get().getGroup("special").value.toLowerCase()); + swift = this.config.getSwiftYear(trimmedText); + year = swift < -1 ? Constants.InvalidYear : year; + ret.setTimex(TimexUtility.generateYearTimex(year, specialYearPrefixes)); + ret.setSuccess(true); + return ret; + } + + LocalDateTime beginDate = DateUtil.safeCreateFromMinValue(year, 1, 1); + + LocalDateTime endValue = DateUtil.safeCreateFromMinValue(year, 12, 31); + LocalDateTime endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + + if (earlyPrefix) { + endValue = DateUtil.safeCreateFromMinValue(year, 6, 30); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (midPrefix) { + beginDate = DateUtil.safeCreateFromMinValue(year, 4, 1); + endValue = DateUtil.safeCreateFromMinValue(year, 9, 30); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (latePrefix) { + beginDate = DateUtil.safeCreateFromMinValue(year, 7, 1); + } + + if (earlierPrefix && swift == 0) { + if (endDate.isAfter(referenceDate)) { + endDate = referenceDate; + } + } else if (laterPrefix && swift == 0) { + if (beginDate.isBefore(referenceDate)) { + beginDate = referenceDate; + } + } + + year = isRef ? Constants.InvalidYear : year; + ret.setTimex(TimexUtility.generateYearTimex(year)); + + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + + ret.setSuccess(true); + return ret; + } + } + } else { + return ret; + } + + // only "month" will come to here + LocalDateTime futureStart = DateUtil.safeCreateFromMinValue(futureYear, month, 1); + LocalDateTime futureEnd = inclusiveEndPeriod ? futureStart.plusMonths(1).minusDays(1) : futureStart.plusMonths(1); + + + LocalDateTime pastStart = DateUtil.safeCreateFromMinValue(pastYear, month, 1); + LocalDateTime pastEnd = inclusiveEndPeriod ? pastStart.plusMonths(1).minusDays(1) : pastStart.plusMonths(1); + + if (earlyPrefix) { + futureEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(futureYear, month, 15) : + DateUtil.safeCreateFromMinValue(futureYear, month, 15).plusDays(1); + pastEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(pastYear, month, 15) : + DateUtil.safeCreateFromMinValue(pastYear, month, 15).plusDays(1); + } else if (midPrefix) { + futureStart = DateUtil.safeCreateFromMinValue(futureYear, month, 10); + pastStart = DateUtil.safeCreateFromMinValue(pastYear, month, 10); + futureEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(futureYear, month, 20) : + DateUtil.safeCreateFromMinValue(futureYear, month, 20).plusDays(1); + pastEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(pastYear, month, 20) : + DateUtil.safeCreateFromMinValue(pastYear, month, 20).plusDays(1); + } else if (latePrefix) { + futureStart = DateUtil.safeCreateFromMinValue(futureYear, month, 16); + pastStart = DateUtil.safeCreateFromMinValue(pastYear, month, 16); + } + + if (earlierPrefix && futureEnd.isEqual(pastEnd)) { + if (futureEnd.isAfter(referenceDate)) { + futureEnd = pastEnd = referenceDate; + } + } else if (laterPrefix && futureStart.isEqual(pastStart)) { + if (futureStart.isBefore(referenceDate)) { + futureStart = pastStart = referenceDate; + } + } + + ret.setFutureValue(new Pair<>(futureStart, futureEnd)); + + ret.setPastValue(new Pair<>(pastStart, pastEnd)); + + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseMonthWithYear(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + ConditionalMatch match = RegexExtension.matchExact(this.config.getMonthWithYear(), text, true); + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getMonthNumWithYear(), text, true); + } + + if (match.getSuccess()) { + String monthStr = match.getMatch().get().getGroup("month").value.toLowerCase(); + String orderStr = match.getMatch().get().getGroup("order").value.toLowerCase(); + + int month = this.config.getMonthOfYear().get(monthStr.toLowerCase()); + + int year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(orderStr); + if (swift < -1) { + return ret; + } + year = referenceDate.getYear() + swift; + } + + LocalDateTime startValue = DateUtil.safeCreateFromMinValue(year, month, 1); + LocalDateTime endValue = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(year, month, 1).plusMonths(1).minusDays(1) : + DateUtil.safeCreateFromMinValue(year, month, 1).plusMonths(1); + + ret.setFutureValue(new Pair<>(startValue, endValue)); + ret.setPastValue(new Pair<>(startValue, endValue)); + + ret.setTimex(String.format("%04d-%02d", year, month)); + + ret.setSuccess(true); + } + + return ret; + } + + private DateTimeResolutionResult parseYear(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int year = Constants.InvalidYear; + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getYearPeriodRegex(), text)).findFirst(); + Optional matchMonth = Arrays.stream(RegExpUtility.getMatches(this.config.getMonthWithYear(), text)).findFirst(); + ; + + if (match.isPresent() && !matchMonth.isPresent()) { + int beginYear = Constants.InvalidYear; + int endYear = Constants.InvalidYear; + + Match[] matches = RegExpUtility.getMatches(this.config.getYearRegex(), text); + if (matches.length == 2) { + // (from|during|in|between)? 2012 (till|to|until|through|-) 2015 + if (!matches[0].value.equals("")) { + beginYear = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(matches[0]); + if (!(beginYear >= Constants.MinYearNum && beginYear <= Constants.MaxYearNum)) { + beginYear = Constants.InvalidYear; + } + } + + if (!matches[1].value.equals("")) { + endYear = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(matches[1]); + if (!(endYear >= Constants.MinYearNum && endYear <= Constants.MaxYearNum)) { + endYear = Constants.InvalidYear; + } + } + } + + if (beginYear != Constants.InvalidYear && endYear != Constants.InvalidYear) { + LocalDateTime beginDay = DateUtil.safeCreateFromMinValue(beginYear, 1, 1); + + LocalDateTime endDayValue = DateUtil.safeCreateFromMinValue(endYear, 1, 1); + LocalDateTime endDay = inclusiveEndPeriod ? endDayValue.minusDays(1) : endDayValue; + + ret.setTimex(String.format("(%s,%s,P%sY)", DateTimeFormatUtil.luisDate(beginDay), DateTimeFormatUtil.luisDate(endDay), (endYear - beginYear))); + ret.setFutureValue(new Pair<>(beginDay, endDay)); + ret.setPastValue(new Pair<>(beginDay, endDay)); + ret.setSuccess(true); + + return ret; + } + } else { + ConditionalMatch exactMatch = RegexExtension.matchExact(this.config.getYearRegex(), text, true); + if (exactMatch.getSuccess()) { + year = this.config.getDateExtractor().getYearFromText(exactMatch.getMatch().get()); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + year = Constants.InvalidYear; + } + } else { + exactMatch = RegexExtension.matchExact(this.config.getYearPlusNumberRegex(), text, true); + if (exactMatch.getSuccess()) { + year = this.config.getDateExtractor().getYearFromText(exactMatch.getMatch().get()); + if (!StringUtility.isNullOrEmpty(exactMatch.getMatch().get().getGroup("special").value)) { + String specialYearPrefixes = this.config.getSpecialYearPrefixesMap().get(exactMatch.getMatch().get().getGroup("special").value.toLowerCase()); + ret.setTimex(TimexUtility.generateYearTimex(year, specialYearPrefixes)); + ret.setSuccess(true); + return ret; + } + } + } + + if (year != Constants.InvalidYear) { + LocalDateTime beginDay = DateUtil.safeCreateFromMinValue(year, 1, 1); + + LocalDateTime endDayValue = DateUtil.safeCreateFromMinValue(year + 1, 1, 1); + LocalDateTime endDay = inclusiveEndPeriod ? endDayValue.minusDays(1) : endDayValue; + + ret.setTimex(TimexUtility.generateYearTimex(year)); + ret.setFutureValue(new Pair<>(beginDay, endDay)); + ret.setPastValue(new Pair<>(beginDay, endDay)); + ret.setSuccess(true); + + return ret; + } + } + + return ret; + } + + // parse entities that made up by two time points + private DateTimeResolutionResult mergeTwoTimePoints(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + List er = this.config.getDateExtractor().extract(text, referenceDate); + DateTimeParseResult pr1 = null; + DateTimeParseResult pr2 = null; + if (er.size() < 2) { + er = this.config.getDateExtractor().extract(this.config.getTokenBeforeDate() + text, referenceDate); + if (er.size() >= 2) { + er.get(0).setStart(er.get(0).getStart() - this.config.getTokenBeforeDate().length()); + er.get(1).setStart(er.get(1).getStart() - this.config.getTokenBeforeDate().length()); + er.set(0, er.get(0)); + er.set(1, er.get(1)); + } else { + DateTimeParseResult nowPr = parseNowAsDate(text, referenceDate); + if (nowPr == null || er.size() < 1) { + return ret; + } + + DateTimeParseResult datePr = this.config.getDateParser().parse(er.get(0), referenceDate); + pr1 = datePr.getStart() < nowPr.getStart() ? datePr : nowPr; + pr2 = datePr.getStart() < nowPr.getStart() ? nowPr : datePr; + } + + } + if (er.size() >= 2) { + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekWithWeekDayRangeRegex(), text)).findFirst(); + String weekPrefix = null; + if (match.isPresent()) { + weekPrefix = match.get().getGroup("week").value; + } + + if (!StringUtility.isNullOrEmpty(weekPrefix)) { + er.get(0).setText(String.format("%s %s", weekPrefix, er.get(0).getText())); + er.get(1).setText(String.format("%s %s", weekPrefix, er.get(1).getText())); + er.set(0, er.get(0)); + er.set(1, er.get(1)); + } + + DateContext dateContext = getYearContext(er.get(0).getText(), er.get(1).getText(), text); + + pr1 = this.config.getDateParser().parse(er.get(0), referenceDate); + pr2 = this.config.getDateParser().parse(er.get(1), referenceDate); + + if (pr1.getValue() == null || pr2.getValue() == null) { + return ret; + } + + pr1 = dateContext.processDateEntityParsingResult(pr1); + pr2 = dateContext.processDateEntityParsingResult(pr2); + + // When the case has no specified year, we should sync the future/past year due to invalid date Feb 29th. + if (dateContext.isEmpty() && (DateContext.isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue()) || + DateContext.isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue()))) { + + HashMap parseResultHashMap = dateContext.syncYear(pr1, pr2); + pr1 = parseResultHashMap.get(Constants.ParseResult1); + pr2 = parseResultHashMap.get(Constants.ParseResult2); + } + } + + List subDateTimeEntities = new ArrayList(); + subDateTimeEntities.add(pr1); + subDateTimeEntities.add(pr2); + ret.setSubDateTimeEntities(subDateTimeEntities); + + LocalDateTime futureBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue(); + LocalDateTime futureEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue(); + + LocalDateTime pastBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getPastValue(); + LocalDateTime pastEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getPastValue(); + + if (futureBegin.isAfter(futureEnd)) { + futureBegin = pastBegin; + } + + if (pastEnd.isBefore(pastBegin)) { + pastEnd = futureEnd; + } + + ret.setTimex(TimexUtility.generateDatePeriodTimexStr(futureBegin, futureEnd, DatePeriodTimexType.ByDay, pr1.getTimexStr(), pr2.getTimexStr())); + + if (pr1.getTimexStr().startsWith(Constants.TimexFuzzyYear) && futureBegin.compareTo(DateUtil.safeCreateFromMinValue(futureBegin.getYear(), 2, 28)) <= 0 && + futureEnd.compareTo(DateUtil.safeCreateFromMinValue(futureBegin.getYear(), 3, 1)) >= 0) { + + // Handle cases like "Feb 29th to March 1st". + // There may be different timexes for FutureValue and PastValue due to the different validity of Feb 29th. + ret.setComment(Constants.Comment_DoubleTimex); + String pastTimex = TimexUtility.generateDatePeriodTimexStr(pastBegin, pastEnd, DatePeriodTimexType.ByDay, pr1.getTimexStr(), pr2.getTimexStr()); + ret.setTimex(TimexUtility.mergeTimexAlternatives(ret.getTimex(), pastTimex)); + } + ret.setFutureValue(new Pair<>(futureBegin, futureEnd)); + ret.setPastValue(new Pair<>(pastBegin, pastEnd)); + ret.setSuccess(true); + + return ret; + } + + // parse entities that made up by two time points with now + private DateTimeParseResult parseNowAsDate(String text, LocalDateTime referenceDate) { + DateTimeParseResult nowPr = null; + LocalDateTime value = referenceDate.toLocalDate().atStartOfDay(); + Match[] matches = RegExpUtility.getMatches(this.config.getNowRegex(), text); + for (Match match : matches) { + DateTimeResolutionResult retNow = new DateTimeResolutionResult(); + retNow.setTimex(DateTimeFormatUtil.luisDate(value)); + retNow.setFutureValue(value); + retNow.setPastValue(value); + + nowPr = new DateTimeParseResult( + match.index, + match.length, + match.value, + Constants.SYS_DATETIME_DATE, + null, + retNow, + "", + value == null ? "" : ((DateTimeResolutionResult)retNow).getTimex()); + } + + return nowPr; + } + + private DateTimeResolutionResult parseDuration(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + LocalDateTime beginDate = referenceDate; + LocalDateTime endDate = referenceDate; + String timex = ""; + boolean restNowSunday = false; + List dateList = null; + + List durationErs = config.getDurationExtractor().extract(text, referenceDate); + if (durationErs.size() == 1) { + ParseResult durationPr = config.getDurationParser().parse(durationErs.get(0)); + String beforeStr = text.substring(0, (durationPr.getStart() != null) ? durationPr.getStart() : 0).trim().toLowerCase(); + String afterStr = text.substring( + ((durationPr.getStart() != null) ? durationPr.getStart() : 0) + ((durationPr.getLength() != null) ? durationPr.getLength() : 0)) + .trim().toLowerCase(); + + List numbersInSuffix = config.getCardinalExtractor().extract(beforeStr); + List numbersInDuration = config.getCardinalExtractor().extract(durationErs.get(0).getText()); + + // Handle cases like "2 upcoming days", "5 previous years" + if (!numbersInSuffix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult numberEr = numbersInSuffix.stream().findFirst().get(); + String numberText = numberEr.getText(); + String durationText = durationErs.get(0).getText(); + String combinedText = String.format("%s %s", numberText, durationText); + List combinedDurationEr = config.getDurationExtractor().extract(combinedText, referenceDate); + + if (!combinedDurationEr.isEmpty()) { + durationPr = config.getDurationParser().parse(combinedDurationEr.stream().findFirst().get()); + int startIndex = numberEr.getStart() + numberEr.getLength(); + beforeStr = beforeStr.substring(startIndex).trim(); + } + } + + GetModAndDateResult getModAndDateResult = new GetModAndDateResult(); + + if (durationPr.getValue() != null) { + DateTimeResolutionResult durationResult = (DateTimeResolutionResult)durationPr.getValue(); + + if (StringUtility.isNullOrEmpty(durationResult.getTimex())) { + return ret; + } + + Optional prefixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPastRegex(), beforeStr)).findFirst(); + Optional suffixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPastRegex(), afterStr)).findFirst(); + if (prefixMatch.isPresent() || suffixMatch.isPresent()) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), false); + beginDate = getModAndDateResult.beginDate; + } + + // Handle the "within two weeks" case which means from today to the end of next two weeks + // Cases like "within 3 days before/after today" is not handled here (4th condition) + boolean isMatch = false; + if (RegexExtension.isExactMatch(config.getWithinNextPrefixRegex(), beforeStr, true)) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + + // In GetModAndDate, this "future" resolution will add one day to beginDate/endDate, but for the "within" case it should start from the current day. + beginDate = beginDate.minusDays(1); + endDate = endDate.minusDays(1); + isMatch = true; + } + + if (RegexExtension.isExactMatch(config.getFutureRegex(), beforeStr, true)) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + isMatch = true; + } + + Optional futureSuffixMatch = Arrays.stream(RegExpUtility.getMatches(config.getFutureSuffixRegex(), afterStr)).findFirst(); + if (futureSuffixMatch.isPresent()) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + } + + // Handle the "in two weeks" case which means the second week + if (RegexExtension.isExactMatch(config.getInConnectorRegex(), beforeStr, true) && + !DurationParsingUtil.isMultipleDuration(durationResult.getTimex()) && !isMatch) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + + // Change the duration value and the beginDate + String unit = durationResult.getTimex().substring(durationResult.getTimex().length() - 1); + + durationResult.setTimex(String.format("P1%s", unit)); + beginDate = DurationParsingUtil.shiftDateTime(durationResult.getTimex(), endDate, false); + } + + if (!StringUtility.isNullOrEmpty(getModAndDateResult.mod)) { + ((DateTimeResolutionResult)durationPr.getValue()).setMod(getModAndDateResult.mod); + } + + timex = durationResult.getTimex(); + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(durationPr); + ret.setSubDateTimeEntities(subDateTimeEntities); + + if (getModAndDateResult.dateList != null) { + ret.setList(getModAndDateResult.dateList.stream().map(e -> (Object)e).collect(Collectors.toList())); + } + } + } + + // Parse "rest of" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getRestOfDateRegex(), text)).findFirst(); + if (match.isPresent()) { + String durationStr = match.get().getGroup("duration").value; + String durationUnit = this.config.getUnitMap().get(durationStr); + switch (durationUnit) { + case "W": + int diff = Constants.WeekDayCount - ((beginDate.getDayOfWeek().getValue()) == 0 ? Constants.WeekDayCount : beginDate.getDayOfWeek().getValue()); + endDate = beginDate.plusDays(diff); + timex = String.format("P%s%s", diff, Constants.TimexDay); + if (diff == 0) { + restNowSunday = true; + } + break; + + case "MON": + endDate = DateUtil.safeCreateFromMinValue(beginDate.getYear(), beginDate.getMonthValue(), 1); + endDate = endDate.plusMonths(1).minusDays(1); + diff = (int)ChronoUnit.DAYS.between(beginDate, endDate) + 1; + timex = String.format("P%s%s", diff, Constants.TimexDay); + break; + + case "Y": + endDate = DateUtil.safeCreateFromMinValue(beginDate.getYear(), 12, 1); + endDate = endDate.plusMonths(1).minusDays(1); + diff = (int)ChronoUnit.DAYS.between(beginDate, endDate) + 1; + timex = String.format("P%s%s", diff, Constants.TimexDay); + break; + default: + break; + } + } + + if (!beginDate.equals(endDate) || restNowSunday) { + endDate = inclusiveEndPeriod ? endDate.minusDays(1) : endDate; + + ret.setTimex(String.format("(%s,%s,%s)", DateTimeFormatUtil.luisDate(beginDate), DateTimeFormatUtil.luisDate(endDate), timex)); + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private GetModAndDateResult getModAndDate(LocalDateTime beginDate, LocalDateTime endDate, LocalDateTime referenceDate, String timex, boolean future) { + LocalDateTime beginDateResult = beginDate; + LocalDateTime endDateResult = endDate; + boolean isBusinessDay = timex.endsWith(Constants.TimexBusinessDay); + int businessDayCount = 0; + + if (isBusinessDay) { + businessDayCount = Integer.parseInt(timex.substring(1, timex.length() - 2)); + } + + if (future) { + String mod = Constants.AFTER_MOD; + + // For future the beginDate should add 1 first + if (isBusinessDay) { + beginDateResult = DurationParsingUtil.getNextBusinessDay(referenceDate); + NthBusinessDayResult nthBusinessDayResult = DurationParsingUtil.getNthBusinessDay(beginDateResult, businessDayCount - 1, true); + endDateResult = nthBusinessDayResult.result.plusDays(1); + return new GetModAndDateResult(beginDateResult, endDateResult, mod, nthBusinessDayResult.dateList); + } else { + beginDateResult = referenceDate.plusDays(1); + endDateResult = DurationParsingUtil.shiftDateTime(timex, beginDateResult, true); + return new GetModAndDateResult(beginDateResult, endDateResult, mod, null); + } + + } else { + String mod = Constants.BEFORE_MOD; + + if (isBusinessDay) { + endDateResult = DurationParsingUtil.getNextBusinessDay(endDateResult, false); + NthBusinessDayResult nthBusinessDayResult = DurationParsingUtil.getNthBusinessDay(endDateResult, businessDayCount - 1, false); + endDateResult = endDateResult.plusDays(1); + beginDateResult = nthBusinessDayResult.result; + return new GetModAndDateResult(beginDateResult, endDateResult, mod, nthBusinessDayResult.dateList); + } else { + beginDateResult = DurationParsingUtil.shiftDateTime(timex, endDateResult, false); + return new GetModAndDateResult(beginDateResult, endDateResult, mod, null); + } + } + } + + // To be consistency, we follow the definition of "week of year": + // "first week of the month" - it has the month's first Thursday in it + // "last week of the month" - it has the month's last Thursday in it + private DateTimeResolutionResult parseWeekOfMonth(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getWeekOfMonthRegex(), trimmedText, true); + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value; + String monthStr = match.getMatch().get().getGroup("month").value; + boolean noYear = false; + int year; + + int month; + if (StringUtility.isNullOrEmpty(monthStr)) { + int swift = this.config.getSwiftDayOrMonth(trimmedText); + + month = referenceDate.plusMonths(swift).getMonthValue(); + year = referenceDate.plusMonths(swift).getYear(); + } else { + month = this.config.getMonthOfYear().get(monthStr); + year = config.getDateExtractor().getYearFromText(match.getMatch().get()); + + if (year == Constants.InvalidYear) { + year = referenceDate.getYear(); + noYear = true; + } + } + + ret = getWeekOfMonth(cardinalStr, month, year, referenceDate, noYear); + + return ret; + } + + private DateTimeResolutionResult parseWeekOfYear(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getWeekOfYearRegex(), trimmedText, true); + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value; + String orderStr = match.getMatch().get().getGroup("order").value.toLowerCase(); + + int year = this.config.getDateExtractor().getYearFromText(match.getMatch().get()); + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(orderStr); + if (swift < -1) { + return ret; + } + year = referenceDate.getYear() + swift; + } + + LocalDateTime targetWeekMonday; + if (this.config.isLastCardinal(cardinalStr)) { + targetWeekMonday = DateUtil.thisDate(getLastThursday(year), DayOfWeek.MONDAY.getValue()); + + ret.setTimex(TimexUtility.generateWeekTimex(targetWeekMonday)); + } else { + int weekNum = this.config.getCardinalMap().get(cardinalStr); + targetWeekMonday = DateUtil.thisDate(getFirstThursday(year), DayOfWeek.MONDAY.getValue()) + .plusDays(Constants.WeekDayCount * (weekNum - 1)); + + ret.setTimex(TimexUtility.generateWeekOfYearTimex(year, weekNum)); + } + + ret.setFutureValue(inclusiveEndPeriod ? + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount))); + + ret.setPastValue(inclusiveEndPeriod ? + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount))); + + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseHalfYear(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getAllHalfYearRegex(), text, true); + + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value.toLowerCase(); + String orderStr = match.getMatch().get().getGroup("order").value.toLowerCase(); + String numberStr = match.getMatch().get().getGroup("number").value; + + int year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(orderStr); + if (swift < -1) { + return ret; + } + year = referenceDate.getYear() + swift; + } + + int halfNum; + if (!StringUtility.isNullOrEmpty(numberStr)) { + halfNum = Integer.parseInt(numberStr); + } else { + halfNum = this.config.getCardinalMap().get(cardinalStr); + } + + LocalDateTime beginDate = DateUtil.safeCreateFromMinValue(year, (halfNum - 1) * Constants.SemesterMonthCount + 1, 1); + LocalDateTime endDate = DateUtil.safeCreateFromMinValue(year, halfNum * Constants.SemesterMonthCount, 1).plusMonths(1); + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + ret.setTimex(String.format("(%s,%s,P6M)", DateTimeFormatUtil.luisDate(beginDate), DateTimeFormatUtil.luisDate(endDate))); + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseQuarter(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getQuarterRegex(), text, true); + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getQuarterRegexYearFront(), text, true); + } + + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value.toLowerCase(); + String orderQuarterStr = match.getMatch().get().getGroup("orderQuarter").value.toLowerCase(); + String orderStr = StringUtility.isNullOrEmpty(orderQuarterStr) ? match.getMatch().get().getGroup("order").value.toLowerCase() : null; + String numberStr = match.getMatch().get().getGroup("number").value; + + boolean noSpecificYear = false; + int year = this.config.getDateExtractor().getYearFromText(match.getMatch().get()); + + if (year == Constants.InvalidYear) { + int swift = StringUtility.isNullOrEmpty(orderQuarterStr) ? this.config.getSwiftYear(orderStr) : 0; + if (swift < -1) { + swift = 0; + noSpecificYear = true; + } + year = referenceDate.getYear() + swift; + } + + int quarterNum; + if (!StringUtility.isNullOrEmpty(numberStr)) { + quarterNum = Integer.parseInt(numberStr); + } else if (!StringUtility.isNullOrEmpty(orderQuarterStr)) { + int month = referenceDate.getMonthValue(); + quarterNum = (int)Math.ceil((double)month / Constants.TrimesterMonthCount); + int swift = this.config.getSwiftYear(orderQuarterStr); + quarterNum += swift; + if (quarterNum <= 0) { + quarterNum += Constants.QuarterCount; + year -= 1; + } else if (quarterNum > Constants.QuarterCount) { + quarterNum -= Constants.QuarterCount; + year += 1; + } + } else { + quarterNum = this.config.getCardinalMap().get(cardinalStr); + } + + LocalDateTime beginDate = DateUtil.safeCreateFromMinValue(year, (quarterNum - 1) * Constants.TrimesterMonthCount + 1, 1); + LocalDateTime endDate = DateUtil.safeCreateFromMinValue(year, quarterNum * Constants.TrimesterMonthCount, 1).plusMonths(1); + + if (noSpecificYear) { + if (endDate.compareTo(referenceDate) < 0) { + ret.setPastValue(new Pair<>(beginDate, endDate)); + + LocalDateTime futureBeginDate = DateUtil.safeCreateFromMinValue(year + 1, (quarterNum - 1) * Constants.TrimesterMonthCount + 1, 1); + LocalDateTime futureEndDate = DateUtil.safeCreateFromMinValue(year + 1, quarterNum * Constants.TrimesterMonthCount, 1).plusMonths(1); + ret.setFutureValue(new Pair<>(futureBeginDate, futureEndDate)); + } else if (endDate.compareTo(referenceDate) > 0) { + ret.setFutureValue(new Pair<>(beginDate, endDate)); + + LocalDateTime pastBeginDate = DateUtil.safeCreateFromMinValue(year - 1, (quarterNum - 1) * Constants.TrimesterMonthCount + 1, 1); + LocalDateTime pastEndDate = DateUtil.safeCreateFromMinValue(year - 1, quarterNum * Constants.TrimesterMonthCount, 1).plusMonths(1); + ret.setPastValue(new Pair<>(pastBeginDate, pastEndDate)); + } else { + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + } + + ret.setTimex(String.format("(%s,%s,P3M)", DateTimeFormatUtil.luisDate(-1, beginDate.getMonthValue(), 1), DateTimeFormatUtil.luisDate(-1, endDate.getMonthValue(), 1))); + } else { + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + ret.setTimex(String.format("(%s,%s,P3M)", DateTimeFormatUtil.luisDate(beginDate), DateTimeFormatUtil.luisDate(endDate))); + } + + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseSeason(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getSeasonRegex(), text, true); + if (match.getSuccess()) { + String seasonStr = this.config.getSeasonMap().get(match.getMatch().get().getGroup("seas").value.toLowerCase()); + + if (!match.getMatch().get().getGroup("EarlyPrefix").value.equals("")) { + ret.setMod(Constants.EARLY_MOD); + } else if (!match.getMatch().get().getGroup("MidPrefix").value.equals("")) { + ret.setMod(Constants.MID_MOD); + } else if (!match.getMatch().get().getGroup("LatePrefix").value.equals("")) { + ret.setMod(Constants.LATE_MOD); + } + + int year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(text); + if (swift < -1) { + ret.setTimex(seasonStr); + ret.setSuccess(true); + return ret; + } + year = referenceDate.getYear() + swift; + } + + String yearStr = String.format("%04d", year); + ret.setTimex(String.format("%s-%s", yearStr, seasonStr)); + + ret.setSuccess(true); + return ret; + } + return ret; + } + + private DateTimeResolutionResult parseWeekOfDate(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getWeekOfRegex(), text)).findFirst(); + List dateErs = config.getDateExtractor().extract(text, referenceDate); + + if (dateErs.isEmpty()) { + // For cases like "week of the 18th" + dateErs.addAll( + config.getCardinalExtractor().extract(text).stream() + .peek(o -> o.setType(Constants.SYS_DATETIME_DATE)) + .filter(o -> dateErs.stream().noneMatch(er -> er.isOverlap(o))) + .collect(Collectors.toList())); + } + + if (match.isPresent() && dateErs.size() == 1) { + DateTimeResolutionResult pr = (DateTimeResolutionResult)config.getDateParser().parse(dateErs.get(0), referenceDate).getValue(); + if (config.getOptions().match(DateTimeOptions.CalendarMode)) { + LocalDateTime monday = DateUtil.thisDate((LocalDateTime)pr.getFutureValue(), DayOfWeek.MONDAY.getValue()); + ret.setTimex(DateTimeFormatUtil.toIsoWeekTimex(monday)); + } else { + ret.setTimex(pr.getTimex()); + } + ret.setComment(Constants.Comment_WeekOf); + ret.setFutureValue(getWeekRangeFromDate((LocalDateTime)pr.getFutureValue())); + ret.setPastValue(getWeekRangeFromDate((LocalDateTime)pr.getPastValue())); + ret.setSuccess(true); + } + return ret; + } + + private DateTimeResolutionResult parseMonthOfDate(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getMonthOfRegex(), text)).findFirst(); + List ex = config.getDateExtractor().extract(text, referenceDate); + + if (match.isPresent() && ex.size() == 1) { + DateTimeResolutionResult pr = (DateTimeResolutionResult)config.getDateParser().parse(ex.get(0), referenceDate).getValue(); + ret.setTimex(pr.getTimex()); + ret.setComment(Constants.Comment_MonthOf); + ret.setFutureValue(getMonthRangeFromDate((LocalDateTime)pr.getFutureValue())); + ret.setPastValue(getMonthRangeFromDate((LocalDateTime)pr.getPastValue())); + ret.setSuccess(true); + } + return ret; + } + + private Pair getWeekRangeFromDate(LocalDateTime date) { + LocalDateTime startDate = DateUtil.thisDate(date, DayOfWeek.MONDAY.getValue()); + LocalDateTime endDate = inclusiveEndPeriod ? startDate.plusDays(Constants.WeekDayCount - 1) : startDate.plusDays(Constants.WeekDayCount); + return new Pair<>(startDate, endDate); + } + + private Pair getMonthRangeFromDate(LocalDateTime date) { + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(date.getYear(), date.getMonthValue(), 1); + LocalDateTime endDate; + + if (date.getMonthValue() < 12) { + endDate = DateUtil.safeCreateFromMinValue(date.getYear(), date.getMonthValue() + 1, 1); + } else { + endDate = DateUtil.safeCreateFromMinValue(date.getYear() + 1, 1, 1); + } + + endDate = inclusiveEndPeriod ? endDate.minusDays(1) : endDate; + return new Pair<>(startDate, endDate); + } + + private DateTimeResolutionResult parseWhichWeek(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getWhichWeekRegex(), text, true); + if (match.getSuccess()) { + int num = Integer.parseInt(match.getMatch().get().getGroup("number").value); + int year = referenceDate.getYear(); + ret.setTimex(String.format("%04d-W%02d", year, num)); + LocalDateTime firstDay = DateUtil.safeCreateFromMinValue(year, 1, 1); + LocalDateTime firstThursday = DateUtil.thisDate(firstDay, DayOfWeek.of(4).getValue()); + + if (DateUtil.weekOfYear(firstThursday) == 1) { + num -= 1; + } + + LocalDateTime value = firstThursday.plusDays(Constants.WeekDayCount * num - 3); + ret.setFutureValue(new Pair<>(value, value.plusDays(7))); + ret.setPastValue(new Pair<>(value, value.plusDays(7))); + ret.setSuccess(true); + } + return ret; + } + + private DateTimeResolutionResult getWeekOfMonth(String cardinalStr, int month, int year, LocalDateTime referenceDate, boolean noYear) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + LocalDateTime targetMonday = getMondayOfTargetWeek(cardinalStr, month, year); + + LocalDateTime futureDate = targetMonday; + LocalDateTime pastDate = targetMonday; + + if (noYear && futureDate.isBefore(referenceDate)) { + futureDate = getMondayOfTargetWeek(cardinalStr, month, year + 1); + } + + if (noYear && pastDate.compareTo(referenceDate) >= 0) { + pastDate = getMondayOfTargetWeek(cardinalStr, month, year - 1); + } + + if (noYear) { + year = Constants.InvalidYear; + } + + // Note that if the cardinalStr equals to "last", the weekNumber would be fixed at "5" + // This may lead to some inconsistency between Timex and Resolution + // the StartDate and EndDate of the resolution would always be correct (following ISO week definition) + // But week number for "last week" might be inconsistency with the resolution as we only have one Timex, + // but we may have past and future resolution which may have different week number + int weekNum = getWeekNumberForMonth(cardinalStr); + + String timex = TimexUtility.generateWeekOfMonthTimex(year, month, weekNum); + ret.setTimex(timex); + + ret.setFutureValue(inclusiveEndPeriod ? + new Pair<>(futureDate, futureDate.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(futureDate, futureDate.plusDays(Constants.WeekDayCount))); + ret.setPastValue(inclusiveEndPeriod ? + new Pair<>(pastDate, pastDate.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(pastDate, pastDate.plusDays(Constants.WeekDayCount))); + + ret.setSuccess(true); + + return ret; + } + + private LocalDateTime getFirstThursday(int year) { + return getFirstThursday(year, Constants.InvalidMonth); + } + + private LocalDateTime getFirstThursday(int year, int month) { + int targetMonth = month; + + if (month == Constants.InvalidMonth) { + targetMonth = Month.JANUARY.getValue(); + } + + LocalDateTime firstDay = LocalDateTime.of(year, targetMonth, 1, 0, 0); + LocalDateTime firstThursday = DateUtil.thisDate(firstDay, DayOfWeek.THURSDAY.getValue()); + + // Thursday fall into next year or next month + if (firstThursday.getMonthValue() != targetMonth) { + firstThursday = firstThursday.plusDays(Constants.WeekDayCount); + } + + return firstThursday; + } + + private LocalDateTime getLastThursday(int year) { + return getLastThursday(year, Constants.InvalidMonth); + } + + private LocalDateTime getLastThursday(int year, int month) { + int targetMonth = month; + + if (month == Constants.InvalidMonth) { + targetMonth = Month.DECEMBER.getValue(); + } + + LocalDateTime lastDay = getLastDay(year, targetMonth); + LocalDateTime lastThursday = DateUtil.thisDate(lastDay, DayOfWeek.THURSDAY.getValue()); + + // Thursday fall into next year or next month + if (lastThursday.getMonthValue() != targetMonth) { + lastThursday = lastThursday.minusDays(Constants.WeekDayCount); + } + + return lastThursday; + } + + private LocalDateTime getLastDay(int year, int month) { + month++; + if (month == 13) { + year++; + month = 1; + } + + LocalDateTime firstDayOfNextMonth = LocalDateTime.of(year, month, 1, 0, 0); + return firstDayOfNextMonth.minusDays(1); + } + + private LocalDateTime getMondayOfTargetWeek(String cardinalStr, int month, int year) { + LocalDateTime result; + if (config.isLastCardinal(cardinalStr)) { + LocalDateTime lastThursday = getLastThursday(year, month); + result = DateUtil.thisDate(lastThursday, DayOfWeek.MONDAY.getValue()); + } else { + int cardinal = getWeekNumberForMonth(cardinalStr); + LocalDateTime firstThursday = getFirstThursday(year, month); + + result = DateUtil.thisDate(firstThursday, DayOfWeek.MONDAY.getValue()).plusDays(Constants.WeekDayCount * (cardinal - 1)); + } + + return result; + } + + private int getWeekNumberForMonth(String cardinalStr) { + int cardinal; + + if (config.isLastCardinal(cardinalStr)) { + // "last week of month" might not be "5th week of month" + // Sometimes it can also be "4th week of month" depends on specific year and month + // But as we only have one Timex, so we use "5" to indicate last week of month + cardinal = Constants.MaxWeekOfMonth; + } else { + cardinal = config.getCardinalMap().get(cardinalStr); + } + + return cardinal; + } + + private DateTimeResolutionResult parseDecade(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int firstTwoNumOfYear = referenceDate.getYear() / 100; + int decade = 0; + int decadeLastYear = 10; + int swift = 1; + boolean inputCentury = false; + + String trimmedText = text.trim(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getDecadeWithCenturyRegex(), text, true); + String beginLuisStr; + String endLuisStr; + + if (match.getSuccess()) { + + String decadeStr = match.getMatch().get().getGroup("decade").value.toLowerCase(); + if (!IntegerUtility.canParse(decadeStr)) { + if (this.config.getWrittenDecades().containsKey(decadeStr)) { + decade = this.config.getWrittenDecades().get(decadeStr); + } else if (this.config.getSpecialDecadeCases().containsKey(decadeStr)) { + firstTwoNumOfYear = this.config.getSpecialDecadeCases().get(decadeStr) / 100; + decade = this.config.getSpecialDecadeCases().get(decadeStr) % 100; + inputCentury = true; + } + } else { + decade = Integer.parseInt(decadeStr); + } + + String centuryStr = match.getMatch().get().getGroup("century").value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(centuryStr)) { + if (!IntegerUtility.canParse(centuryStr)) { + if (this.config.getNumbers().containsKey(centuryStr)) { + firstTwoNumOfYear = this.config.getNumbers().get(centuryStr); + } else { + // handle the case like "one/two thousand", "one/two hundred", etc. + List er = this.config.getIntegerExtractor().extract(centuryStr); + + if (er.size() == 0) { + return ret; + } + + firstTwoNumOfYear = Math.round(((Double)(this.config.getNumberParser().parse(er.get(0)).getValue() != null ? + this.config.getNumberParser().parse(er.get(0)).getValue() : + 0)).floatValue()); + if (firstTwoNumOfYear >= 100) { + firstTwoNumOfYear = firstTwoNumOfYear / 100; + } + } + } else { + firstTwoNumOfYear = Integer.parseInt(centuryStr); + } + + inputCentury = true; + } + } else { + // handle cases like "the last 2 decades" "the next decade" + match = RegexExtension.matchExact(this.config.getRelativeDecadeRegex(), trimmedText, true); + if (match.getSuccess()) { + inputCentury = true; + + swift = this.config.getSwiftDayOrMonth(trimmedText); + + String numStr = match.getMatch().get().getGroup("number").value.toLowerCase(); + List er = this.config.getIntegerExtractor().extract(numStr); + if (er.size() == 1) { + int swiftNum = Math.round(((Double)(this.config.getNumberParser().parse(er.get(0)).getValue() != null ? + this.config.getNumberParser().parse(er.get(0)).getValue() : + 0)).floatValue()); + swift = swift * swiftNum; + } + + int beginDecade = (referenceDate.getYear() % 100) / 10; + if (swift < 0) { + beginDecade += swift; + } else if (swift > 0) { + beginDecade += 1; + } + + decade = beginDecade * 10; + } else { + return ret; + } + } + + int beginYear = firstTwoNumOfYear * 100 + decade; + // swift = 0 corresponding to the/this decade + int totalLastYear = decadeLastYear * Math.abs(swift == 0 ? 1 : swift); + + if (inputCentury) { + beginLuisStr = DateTimeFormatUtil.luisDate(beginYear, 1, 1); + endLuisStr = DateTimeFormatUtil.luisDate(beginYear + totalLastYear, 1, 1); + } else { + String beginYearStr = String.format("XX%s", decade); + beginLuisStr = DateTimeFormatUtil.luisDate(-1, 1, 1); + beginLuisStr = beginLuisStr.replace("XXXX", beginYearStr); + + String endYearStr = String.format("XX%s", (decade + totalLastYear)); + endLuisStr = DateTimeFormatUtil.luisDate(-1, 1, 1); + endLuisStr = endLuisStr.replace("XXXX", endYearStr); + } + ret.setTimex(String.format("(%s,%s,P%sY)", beginLuisStr, endLuisStr, totalLastYear)); + + int futureYear = beginYear; + int pastYear = beginYear; + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(beginYear, 1, 1); + if (!inputCentury && startDate.isBefore(referenceDate)) { + futureYear += 100; + } + + if (!inputCentury && startDate.compareTo(referenceDate) >= 0) { + pastYear -= 100; + } + + ret.setFutureValue(new Pair<>(DateUtil.safeCreateFromMinValue(futureYear, 1, 1), + DateUtil.safeCreateFromMinValue(futureYear + totalLastYear, 1, 1))); + + ret.setPastValue(new Pair<>(DateUtil.safeCreateFromMinValue(pastYear, 1, 1), + DateUtil.safeCreateFromMinValue(pastYear + totalLastYear, 1, 1))); + + ret.setSuccess(true); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + private DateContext getYearContext(String startDateStr, String endDateStr, String text) { + boolean isEndDatePureYear = false; + boolean isDateRelative = false; + int contextYear = Constants.InvalidYear; + + Optional yearMatchForEndDate = Arrays.stream(RegExpUtility.getMatches(this.config.getYearRegex(), endDateStr)).findFirst(); + + if (yearMatchForEndDate.isPresent() && yearMatchForEndDate.get().length == endDateStr.length()) { + isEndDatePureYear = true; + } + + Optional relativeMatchForStartDate = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeRegex(), startDateStr)).findFirst(); + Optional relativeMatchForEndDate = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeRegex(), endDateStr)).findFirst(); + isDateRelative = relativeMatchForStartDate.isPresent() || relativeMatchForEndDate.isPresent(); + + if (!isEndDatePureYear && !isDateRelative) { + for (Match match : RegExpUtility.getMatches(config.getYearRegex(), text)) { + int year = config.getDateExtractor().getYearFromText(match); + + if (year != Constants.InvalidYear) { + if (contextYear == Constants.InvalidYear) { + contextYear = year; + } else { + // This indicates that the text has two different year value, no common context year + if (contextYear != year) { + contextYear = Constants.InvalidYear; + break; + } + } + } + } + } + + DateContext dateContext = new DateContext(); + dateContext.setYear(contextYear); + return dateContext; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java new file mode 100644 index 000000000..5eb012151 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java @@ -0,0 +1,256 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtendedModelResult; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import org.javatuples.Pair; + +public class BaseDateTimeAltParser implements IDateTimeParser { + + private static final String parserName = Constants.SYS_DATETIME_DATETIMEALT; + private final IDateTimeAltParserConfiguration config; + + public BaseDateTimeAltParser(IDateTimeAltParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + DateTimeResolutionResult value = null; + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = parseDateTimeAndTimeAlt(er, reference); + + if (innerResult.getSuccess()) { + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : value.getTimex()); + + return ret; + } + + // merge the entity with its related contexts and then parse the combine text + private DateTimeResolutionResult parseDateTimeAndTimeAlt(ExtractResult er, LocalDateTime referenceTime) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + // Original type of the extracted entity + String subType = ((Map)(er.getData())).get(Constants.SubType).toString(); + ExtractResult dateTimeEr = new ExtractResult(); + + // e.g. {next week Mon} or {Tue}, formmer--"next week Mon" doesn't contain "context" key + boolean hasContext = false; + ExtractResult contextEr = null; + if (((Map)er.getData()).containsKey(Constants.Context)) { + contextEr = (ExtractResult)((Map)er.getData()).get(Constants.Context); + if (contextEr.getType().equals(Constants.ContextType_RelativeSuffix)) { + dateTimeEr.setText(String.format("%s %s", er.getText(), contextEr.getText())); + } else { + dateTimeEr.setText(String.format("%s %s", contextEr.getText(), er.getText())); + } + + hasContext = true; + } else { + dateTimeEr.setText(er.getText()); + } + + dateTimeEr.setData(er.getData()); + DateTimeParseResult dateTimePr = null; + + if (subType.equals(Constants.SYS_DATETIME_DATE)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATE); + dateTimePr = this.config.getDateParser().parse(dateTimeEr, referenceTime); + } else if (subType.equals(Constants.SYS_DATETIME_TIME)) { + if (!hasContext) { + dateTimeEr.setType(Constants.SYS_DATETIME_TIME); + dateTimePr = this.config.getTimeParser().parse(dateTimeEr, referenceTime); + } else if (contextEr.getType().equals(Constants.SYS_DATETIME_DATE) || contextEr.getType().equals(Constants.ContextType_RelativePrefix)) { + // For cases: + // Monday 9 am or 11 am + // next 9 am or 11 am + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIME); + dateTimePr = this.config.getDateTimeParser().parse(dateTimeEr, referenceTime); + } else if (contextEr.getType().equals(Constants.ContextType_AmPm)) { + // For cases: in the afternoon 3 o'clock or 5 o'clock + dateTimeEr.setType(Constants.SYS_DATETIME_TIME); + dateTimePr = this.config.getTimeParser().parse(dateTimeEr, referenceTime); + } + } else if (subType.equals(Constants.SYS_DATETIME_DATETIME)) { + // "next week Mon 9 am or Tue 1 pm" + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIME); + dateTimePr = this.config.getDateTimeParser().parse(dateTimeEr, referenceTime); + } else if (subType.equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + if (!hasContext) { + dateTimeEr.setType(Constants.SYS_DATETIME_TIMEPERIOD); + dateTimePr = this.config.getTimePeriodParser().parse(dateTimeEr, referenceTime); + } else if (contextEr.getType().equals(Constants.SYS_DATETIME_DATE) || contextEr.getType().equals(Constants.ContextType_RelativePrefix)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIMEPERIOD); + dateTimePr = this.config.getDateTimePeriodParser().parse(dateTimeEr, referenceTime); + } + } else if (subType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIMEPERIOD); + dateTimePr = this.config.getDateTimePeriodParser().parse(dateTimeEr, referenceTime); + } else if (subType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATEPERIOD); + dateTimePr = this.config.getDatePeriodParser().parse(dateTimeEr, referenceTime); + } + + if (dateTimePr != null && dateTimePr.getValue() != null) { + ret.setFutureValue(((DateTimeResolutionResult)dateTimePr.getValue()).getFutureValue()); + ret.setPastValue(((DateTimeResolutionResult)dateTimePr.getValue()).getPastValue()); + ret.setTimex(dateTimePr.getTimexStr()); + + // Create resolution + getResolution(er, dateTimePr, ret); + + ret.setSuccess(true); + } + + return ret; + } + + private void getResolution(ExtractResult er, DateTimeParseResult pr, DateTimeResolutionResult ret) { + String parentText = ((Map)er.getData()).get(ExtendedModelResult.ParentTextKey).toString(); + String type = pr.getType(); + + boolean isPeriod = false; + boolean isSinglePoint = false; + String singlePointResolution = ""; + String pastStartPointResolution = ""; + String pastEndPointResolution = ""; + String futureStartPointResolution = ""; + String futureEndPointResolution = ""; + String singlePointType = ""; + String startPointType = ""; + String endPointType = ""; + + if (type.equals(Constants.SYS_DATETIME_DATEPERIOD) || type.equalsIgnoreCase(Constants.SYS_DATETIME_TIMEPERIOD) || + type.equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + isPeriod = true; + switch (type) { + case Constants.SYS_DATETIME_DATEPERIOD: + startPointType = TimeTypeConstants.START_DATE; + endPointType = TimeTypeConstants.END_DATE; + pastStartPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getPastValue()).getValue0()); + pastEndPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getPastValue()).getValue1()); + futureStartPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getFutureValue()).getValue0()); + futureEndPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getFutureValue()).getValue1()); + break; + + case Constants.SYS_DATETIME_DATETIMEPERIOD: + startPointType = TimeTypeConstants.START_DATETIME; + endPointType = TimeTypeConstants.END_DATETIME; + + if (ret.getPastValue() instanceof Pair) { + pastStartPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getPastValue()).getValue0()); + pastEndPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getPastValue()).getValue1()); + futureStartPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getFutureValue()).getValue0()); + futureEndPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getFutureValue()).getValue1()); + } else if (ret.getPastValue() instanceof LocalDateTime) { + pastStartPointResolution = DateTimeFormatUtil.formatDateTime((LocalDateTime)ret.getPastValue()); + futureStartPointResolution = DateTimeFormatUtil.formatDateTime((LocalDateTime)ret.getFutureValue()); + } + + break; + + case Constants.SYS_DATETIME_TIMEPERIOD: + startPointType = TimeTypeConstants.START_TIME; + endPointType = TimeTypeConstants.END_TIME; + pastStartPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getPastValue()).getValue0()); + pastEndPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getPastValue()).getValue1()); + futureStartPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getFutureValue()).getValue0()); + futureEndPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getFutureValue()).getValue1()); + break; + default: + break; + } + } else { + isSinglePoint = true; + switch (type) { + case Constants.SYS_DATETIME_DATE: + singlePointType = TimeTypeConstants.DATE; + singlePointResolution = DateTimeFormatUtil.formatDate((LocalDateTime)ret.getFutureValue()); + break; + + case Constants.SYS_DATETIME_DATETIME: + singlePointType = TimeTypeConstants.DATETIME; + singlePointResolution = DateTimeFormatUtil.formatDateTime((LocalDateTime)ret.getFutureValue()); + break; + + case Constants.SYS_DATETIME_TIME: + singlePointType = TimeTypeConstants.TIME; + singlePointResolution = DateTimeFormatUtil.formatTime((LocalDateTime)ret.getFutureValue()); + break; + default: + break; + } + } + + if (isPeriod) { + ret.setFutureResolution(ImmutableMap.builder() + .put(startPointType, futureStartPointResolution) + .put(endPointType, futureEndPointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + + ret.setPastResolution(ImmutableMap.builder() + .put(startPointType, pastStartPointResolution) + .put(endPointType, pastEndPointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + } else if (isSinglePoint) { + ret.setFutureResolution(ImmutableMap.builder() + .put(singlePointType, singlePointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + + ret.setPastResolution(ImmutableMap.builder() + .put(singlePointType, singlePointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + } + + if (((DateTimeResolutionResult)pr.getValue()).getMod() != null) { + ret.setMod(((DateTimeResolutionResult)pr.getValue()).getMod()); + } + + if (((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution()); + } + } + + @Override + public List filterResults(String query, List candidateResults) { + + return candidateResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java new file mode 100644 index 000000000..835a21d7c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java @@ -0,0 +1,398 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BaseDateTimeParser implements IDateTimeParser { + + private final IDateTimeParserConfiguration config; + + public BaseDateTimeParser(IDateTimeParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATETIME; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = this.mergeDateAndTime(er.getText(), referenceDate); + + if (!innerResult.getSuccess()) { + innerResult = this.parseBasicRegex(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseTimeOfToday(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseSpecialTimeOfDate(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parserDurationWithAgoAndLater(er.getText(), referenceDate); + } + + if (innerResult.getSuccess()) { + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.DATETIME, + DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getFutureValue())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.DATETIME, + DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getPastValue())) + .build(); + innerResult.setPastResolution(pastResolution); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), value, "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + // Merge a Date entity and a Time entity + private DateTimeResolutionResult mergeDateAndTime(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + List ersDate = config.getDateExtractor().extract(text, reference); + if (ersDate.isEmpty()) { + ersDate = config.getDateExtractor().extract(config.getTokenBeforeDate() + text, reference); + if (ersDate.size() == 1) { + int newStart = ersDate.get(0).getStart() - config.getTokenBeforeDate().length(); + ersDate.get(0).setStart(newStart); + ersDate.set(0, ersDate.get(0)); + } else { + return result; + } + } else { + // This is to understand if there is an ambiguous token in the text. For some + // languages (e.g. spanish), + // the same word could mean different things (e.g a time in the day or an + // specific day). + if (config.containsAmbiguousToken(text, ersDate.get(0).getText())) { + return result; + } + } + + List ersTime = config.getTimeExtractor().extract(text, reference); + if (ersTime.isEmpty()) { + // Here we filter out "morning, afternoon, night..." time entities + ersTime = config.getTimeExtractor().extract(config.getTokenBeforeTime() + text, reference); + if (ersTime.size() == 1) { + int newStart = ersTime.get(0).getStart() - config.getTokenBeforeTime().length(); + ersTime.get(0).setStart(newStart); + ersTime.set(0, ersTime.get(0)); + } else if (ersTime.isEmpty()) { + // check whether there is a number being used as a time point + boolean hasTimeNumber = false; + List numErs = config.getIntegerExtractor().extract(text); + if (!numErs.isEmpty() && ersDate.size() == 1) { + for (ExtractResult num : numErs) { + int middleBegin = ersDate.get(0).getStart() + ersDate.get(0).getLength(); + int middleEnd = num.getStart(); + if (middleBegin > middleEnd) { + continue; + } + + String middleStr = text.substring(middleBegin, middleEnd).trim().toLowerCase(); + Optional match = Arrays + .stream(RegExpUtility.getMatches(config.getDateNumberConnectorRegex(), middleStr)) + .findFirst(); + if (StringUtility.isNullOrEmpty(middleStr) || match.isPresent()) { + num.setType(Constants.SYS_DATETIME_TIME); + ersTime.add(num); + hasTimeNumber = true; + } + } + } + + if (!hasTimeNumber) { + return result; + } + } + } + + // Handle cases like "Oct. 5 in the afternoon at 7:00"; + // in this case "5 in the afternoon" will be extracted as a Time entity + int correctTimeIdx = 0; + while (correctTimeIdx < ersTime.size() && ersTime.get(correctTimeIdx).isOverlap(ersDate.get(0))) { + correctTimeIdx++; + } + + if (correctTimeIdx >= ersTime.size()) { + return result; + } + + DateTimeParseResult prDate = config.getDateParser().parse(ersDate.get(0), reference); + DateTimeParseResult prTime = config.getTimeParser().parse(ersTime.get(correctTimeIdx), reference); + + if (prDate.getValue() == null || prTime.getValue() == null) { + return result; + } + + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)prDate.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)prDate.getValue()).getPastValue(); + LocalDateTime time = (LocalDateTime)((DateTimeResolutionResult)prTime.getValue()).getPastValue(); + + int hour = time.getHour(); + int min = time.getMinute(); + int sec = time.getSecond(); + + // Handle morning, afternoon + if (RegExpUtility.getMatches(config.getPMTimeRegex(), text).length != 0 && withinAfternoonHours(hour)) { + hour += Constants.HalfDayHourCount; + } else if (RegExpUtility.getMatches(config.getAMTimeRegex(), text).length != 0 && + withinMorningHoursAndNoon(hour, min, sec)) { + hour -= Constants.HalfDayHourCount; + } + + String timeStr = prTime.getTimexStr(); + if (timeStr.endsWith(Constants.Comment_AmPm)) { + timeStr = timeStr.substring(0, timeStr.length() - 4); + } + + timeStr = String.format("T%02d%s", hour, timeStr.substring(3)); + result.setTimex(prDate.getTimexStr() + timeStr); + DateTimeResolutionResult val = (DateTimeResolutionResult)prTime.getValue(); + if (hour <= Constants.HalfDayHourCount && RegExpUtility.getMatches(config.getPMTimeRegex(), text).length == 0 && + RegExpUtility.getMatches(config.getAMTimeRegex(), text).length == 0 && + !StringUtility.isNullOrEmpty(val.getComment())) { + result.setComment(Constants.Comment_AmPm); + } + + result.setFutureValue(DateUtil.safeCreateFromMinValue(futureDate.getYear(), futureDate.getMonthValue(), + futureDate.getDayOfMonth(), hour, min, sec)); + result.setPastValue(DateUtil.safeCreateFromMinValue(pastDate.getYear(), pastDate.getMonthValue(), + pastDate.getDayOfMonth(), hour, min, sec)); + + result.setSuccess(true); + + // Change the value of time object + prTime.setTimexStr(timeStr); + if (!StringUtility.isNullOrEmpty(result.getComment())) { + DateTimeResolutionResult newValue = (DateTimeResolutionResult)prTime.getValue(); + newValue.setComment(result.getComment().equals(Constants.Comment_AmPm) ? Constants.Comment_AmPm : ""); + prTime.setValue(newValue); + prTime.setTimexStr(timeStr); + } + + // Add the date and time object in case we want to split them + List entities = new ArrayList<>(); + entities.add(prDate); + entities.add(prTime); + result.setSubDateTimeEntities(entities); + + result.setTimeZoneResolution(((DateTimeResolutionResult)prTime.getValue()).getTimeZoneResolution()); + + return result; + } + + private DateTimeResolutionResult parseBasicRegex(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + + // Handle "now" + if (RegexExtension.isExactMatch(config.getNowRegex(), trimmedText, true)) { + ResultTimex timexResult = config.getMatchedNowTimex(trimmedText); + result.setTimex(timexResult.getTimex()); + result.setFutureValue(reference); + result.setPastValue(reference); + result.setSuccess(true); + } + + return result; + } + + private DateTimeResolutionResult parseTimeOfToday(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + + int hour = 0; + int minute = 0; + int second = 0; + String timeStr; + + ConditionalMatch wholeMatch = RegexExtension.matchExact(config.getSimpleTimeOfTodayAfterRegex(), trimmedText, true); + if (!wholeMatch.getSuccess()) { + wholeMatch = RegexExtension.matchExact(config.getSimpleTimeOfTodayBeforeRegex(), trimmedText, true); + } + + if (wholeMatch.getSuccess()) { + String hourStr = wholeMatch.getMatch().get().getGroup(Constants.HourGroupName).value; + if (StringUtility.isNullOrEmpty(hourStr)) { + hourStr = wholeMatch.getMatch().get().getGroup("hournum").value.toLowerCase(); + hour = config.getNumbers().get(hourStr); + } else { + hour = Integer.parseInt(hourStr); + } + + timeStr = String.format("T%02d", hour); + } else { + List ers = config.getTimeExtractor().extract(trimmedText, reference); + if (ers.size() != 1) { + ers = config.getTimeExtractor().extract(config.getTokenBeforeTime() + trimmedText, reference); + if (ers.size() == 1) { + int newStart = ers.get(0).getStart() - config.getTokenBeforeTime().length(); + ers.get(0).setStart(newStart); + ers.set(0, ers.get(0)); + } else { + return result; + } + } + + DateTimeParseResult pr = config.getTimeParser().parse(ers.get(0), reference); + if (pr.getValue() == null) { + return result; + } + + LocalDateTime time = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + hour = time.getHour(); + minute = time.getMinute(); + second = time.getSecond(); + timeStr = pr.getTimexStr(); + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSpecificTimeOfDayRegex(), trimmedText)) + .findFirst(); + + if (match.isPresent()) { + String matchStr = match.get().value.toLowerCase(); + + // Handle "last", "next" + int swift = config.getSwiftDay(matchStr); + LocalDateTime date = reference.plusDays(swift); + + // Handle "morning", "afternoon" + hour = config.getHour(matchStr, hour); + + // In this situation, timeStr cannot end up with "ampm", because we always have + // a "morning" or "night" + if (timeStr.endsWith(Constants.Comment_AmPm)) { + timeStr = timeStr.substring(0, timeStr.length() - 4); + } + + timeStr = String.format("T%02d%s", hour, timeStr.substring(3)); + + result.setTimex(DateTimeFormatUtil.formatDate(date) + timeStr); + LocalDateTime dateResult = DateUtil.safeCreateFromMinValue(date.getYear(), date.getMonthValue(), + date.getDayOfMonth(), hour, minute, second); + + result.setFutureValue(dateResult); + result.setPastValue(dateResult); + result.setSuccess(true); + } + + return result; + } + + private DateTimeResolutionResult parseSpecialTimeOfDate(String text, LocalDateTime reference) { + DateTimeResolutionResult result = parseUnspecificTimeOfDate(text, reference); + + if (result.getSuccess()) { + return result; + } + + List ers = config.getDateExtractor().extract(text, reference); + if (ers.size() != 1) { + return result; + } + + String beforeStr = text.substring(0, ers.get(0).getStart()); + if (RegExpUtility.getMatches(config.getSpecificEndOfRegex(), beforeStr).length != 0) { + DateTimeParseResult pr = config.getDateParser().parse(ers.get(0), reference); + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + result = resolveEndOfDay(pr.getTimexStr(), futureDate, pastDate); + } + + return result; + } + + private DateTimeResolutionResult parseUnspecificTimeOfDate(String text, LocalDateTime reference) { + // Handle 'eod', 'end of day' + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional eod = Arrays.stream(RegExpUtility.getMatches(config.getUnspecificEndOfRegex(), text)).findFirst(); + + if (eod.isPresent()) { + result = resolveEndOfDay(DateTimeFormatUtil.formatDate(reference), reference, reference); + } + + return result; + } + + private DateTimeResolutionResult resolveEndOfDay(String timexPrefix, LocalDateTime futureDate, LocalDateTime pastDate) { + String timex = String.format("%sT23:59:59", timexPrefix); + LocalDateTime futureValue = LocalDateTime.of(futureDate.toLocalDate(), LocalTime.MIDNIGHT).plusDays(1).minusSeconds(1); + LocalDateTime pastValue = LocalDateTime.of(pastDate.toLocalDate(), LocalTime.MIDNIGHT).plusDays(1).minusSeconds(1); + + DateTimeResolutionResult result = new DateTimeResolutionResult(); + result.setTimex(timex); + result.setFutureValue(futureValue); + result.setPastValue(pastValue); + result.setSuccess(true); + + return result; + } + + private boolean withinAfternoonHours(int hour) { + return hour < Constants.HalfDayHourCount; + } + + private boolean withinMorningHoursAndNoon(int hour, int min, int sec) { + return (hour > Constants.HalfDayHourCount || (hour == Constants.HalfDayHourCount && (min > 0 || sec > 0))); + } + + private DateTimeResolutionResult parserDurationWithAgoAndLater(String text, LocalDateTime reference) { + return AgoLaterUtil.parseDurationWithAgoAndLater(text, reference, config.getDurationExtractor(), + config.getDurationParser(), config.getUnitMap(), config.getUnitRegex(), + config.getUtilityConfiguration(), config::getSwiftDay); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java new file mode 100644 index 000000000..011ceb157 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java @@ -0,0 +1,1036 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RangeTimexComponents; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public class BaseDateTimePeriodParser implements IDateTimeParser { + + protected final IDateTimePeriodParserConfiguration config; + + public BaseDateTimePeriodParser(IDateTimePeriodParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = internalParse(er.getText(), referenceDate); + + if (TimeZoneUtility.shouldResolveTimeZone(er, config.getOptions())) { + Map metadata = (HashMap)er.getData(); + + ExtractResult timezoneEr = (ExtractResult)metadata.get(Constants.SYS_DATETIME_TIMEZONE); + ParseResult timezonePr = config.getTimeZoneParser().parse(timezoneEr); + if (timezonePr.getValue() != null) { + innerResult.setTimeZoneResolution(((DateTimeResolutionResult)timezonePr.getValue()).getTimeZoneResolution()); + } + } + + if (innerResult.getSuccess()) { + if (!isBeforeOrAfterMod(innerResult.getMod())) { + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, + DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getFutureValue()).getValue0())) + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getFutureValue()).getValue1())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getPastValue()).getValue0())) + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getPastValue()).getValue1())) + .build(); + innerResult.setPastResolution(pastResolution); + + } else { + if (innerResult.getMod().equals(Constants.AFTER_MOD)) { + // Cases like "1/1/2015 after 2:00" there is no EndTime + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getFutureValue())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getPastValue())) + .build(); + innerResult.setPastResolution(pastResolution); + } else { + // Cases like "1/1/2015 before 5:00 in the afternoon" there is no StartTime + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getFutureValue())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getPastValue())) + .build(); + innerResult.setPastResolution(pastResolution); + } + } + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + private DateTimeResolutionResult internalParse(String text, LocalDateTime reference) { + DateTimeResolutionResult innerResult = this.mergeDateAndTimePeriods(text, reference); + + if (!innerResult.getSuccess()) { + innerResult = this.mergeTwoTimePoints(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseSpecificTimeOfDay(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseDuration(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseRelativeUnit(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseDateWithPeriodPrefix(text, reference); + } + + if (!innerResult.getSuccess()) { + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + innerResult = this.parseDateWithTimePeriodSuffix(text, reference); + } + + return innerResult; + } + + private boolean isBeforeOrAfterMod(String mod) { + return !StringUtility.isNullOrEmpty(mod) && + (mod == Constants.BEFORE_MOD || mod == Constants.AFTER_MOD); + } + + private DateTimeResolutionResult mergeDateAndTimePeriods(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + + List ers = config.getTimePeriodExtractor().extract(trimmedText, referenceTime); + + if (ers.size() == 0) { + return parsePureNumberCases(text, referenceTime); + } else if (ers.size() == 1) { + ParseResult timePeriodParseResult = config.getTimePeriodParser().parse(ers.get(0)); + DateTimeResolutionResult timePeriodResolutionResult = (DateTimeResolutionResult)timePeriodParseResult.getValue(); + + if (timePeriodResolutionResult == null) { + return parsePureNumberCases(text, referenceTime); + } + + String timePeriodTimex = timePeriodResolutionResult.getTimex(); + + + // If it is a range type timex + if (TimexUtility.isRangeTimex(timePeriodTimex)) { + List dateResult = config.getDateExtractor().extract(trimmedText.replace(ers.get(0).getText(), ""), referenceTime); + String dateText = trimmedText.replace(ers.get(0).getText(), "").replace(config.getTokenBeforeDate(), "").trim(); + + // If only one Date is extracted and the Date text equals to the rest part of source text + if (dateResult.size() == 1 && dateText.equals(dateResult.get(0).getText())) { + String dateTimex; + LocalDateTime futureTime; + LocalDateTime pastTime; + + DateTimeParseResult pr = config.getDateParser().parse(dateResult.get(0), referenceTime); + + if (pr.getValue() != null) { + futureTime = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + pastTime = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + dateTimex = pr.getTimexStr(); + } else { + return parsePureNumberCases(text, referenceTime); + } + + RangeTimexComponents rangeTimexComponents = TimexUtility.getRangeTimexComponents(timePeriodTimex); + if (rangeTimexComponents.isValid) { + String beginTimex = TimexUtility.combineDateAndTimeTimex(dateTimex, rangeTimexComponents.beginTimex); + String endTimex = TimexUtility.combineDateAndTimeTimex(dateTimex, rangeTimexComponents.endTimex); + ret.setTimex(TimexUtility.generateDateTimePeriodTimex(beginTimex, endTimex, rangeTimexComponents.durationTimex)); + + Pair timePeriodFutureValue = (Pair)timePeriodResolutionResult.getFutureValue(); + LocalDateTime beginTime = timePeriodFutureValue.getValue0(); + LocalDateTime endTime = timePeriodFutureValue.getValue1(); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(futureTime.getYear(), futureTime.getMonthValue(), futureTime.getDayOfMonth(), + beginTime.getHour(), beginTime.getMinute(), beginTime.getSecond()), + DateUtil.safeCreateFromMinValue(futureTime.getYear(), futureTime.getMonthValue(), futureTime.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond()) + )); + + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(pastTime.getYear(), pastTime.getMonthValue(), pastTime.getDayOfMonth(), + beginTime.getHour(), beginTime.getMinute(), beginTime.getSecond()), + DateUtil.safeCreateFromMinValue(pastTime.getYear(), pastTime.getMonthValue(), pastTime.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond()) + )); + + + if (!StringUtility.isNullOrEmpty(timePeriodResolutionResult.getComment()) && + timePeriodResolutionResult.getComment().equals(Constants.Comment_AmPm)) { + // AmPm comment is used for later SetParserResult to judge whether this parse comments should have two parsing results + // Cases like "from 10:30 to 11 on 1/1/2015" should have AmPm comment, as it can be parsed to "10:30am to 11am" and also be parsed to "10:30pm to 11pm" + // Cases like "from 10:30 to 3 on 1/1/2015" should not have AmPm comment + if (beginTime.getHour() < Constants.HalfDayHourCount && endTime.getHour() < Constants.HalfDayHourCount) { + ret.setComment(Constants.Comment_AmPm); + } + } + + ret.setSuccess(true); + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(pr); + subDateTimeEntities.add(timePeriodParseResult); + ret.setSubDateTimeEntities(subDateTimeEntities); + + return ret; + } + } + + return parsePureNumberCases(text, referenceTime); + } + } + + return ret; + } + + // Handle cases like "Monday 7-9", where "7-9" can't be extracted by the TimePeriodExtractor + private DateTimeResolutionResult parsePureNumberCases(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPureNumberFromToRegex(), trimmedText)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPureNumberBetweenAndRegex(), trimmedText)).findFirst(); + } + + if (match.isPresent() && (match.get().index == 0 || match.get().index + match.get().length == trimmedText.length())) { + ParseTimePeriodResult parseTimePeriodResult = parseTimePeriod(match.get()); + int beginHour = parseTimePeriodResult.beginHour; + int endHour = parseTimePeriodResult.endHour; + ret.setComment(parseTimePeriodResult.comments); + + String dateStr = ""; + + // Parse following date + List ers = config.getDateExtractor().extract(trimmedText.replace(match.get().value, ""), referenceTime); + LocalDateTime futureDate; + LocalDateTime pastDate; + + if (ers.size() > 0) { + DateTimeParseResult pr = config.getDateParser().parse(ers.get(0), referenceTime); + if (pr.getValue() != null) { + DateTimeResolutionResult prValue = (DateTimeResolutionResult)pr.getValue(); + futureDate = (LocalDateTime)prValue.getFutureValue(); + pastDate = (LocalDateTime)prValue.getPastValue(); + + dateStr = pr.getTimexStr(); + + } else { + return ret; + } + } else { + return ret; + } + + int pastHours = endHour - beginHour; + String beginTimex = TimexUtility.combineDateAndTimeTimex(dateStr, DateTimeFormatUtil.shortTime(beginHour)); + String endTimex = TimexUtility.combineDateAndTimeTimex(dateStr, DateTimeFormatUtil.shortTime(endHour)); + String durationTimex = TimexUtility.generateDurationTimex(endHour - beginHour, Constants.TimexHour, true); + + ret.setTimex(TimexUtility.generateDateTimePeriodTimex(beginTimex, endTimex, durationTimex)); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + endHour, 0, 0) + )); + + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + endHour, 0, 0) + )); + + ret.setSuccess(true); + } + + return ret; + } + + private ParseTimePeriodResult parseTimePeriod(Match match) { + + ParseTimePeriodResult result = new ParseTimePeriodResult(); + + // This "from .. to .." pattern is valid if followed by a Date OR "pm" + boolean hasAm = false; + boolean hasPm = false; + String comments = ""; + + // Get hours + MatchGroup hourGroup = match.getGroup(Constants.HourGroupName); + String hourStr = hourGroup.captures[0].value; + + if (this.config.getNumbers().containsKey(hourStr)) { + result.beginHour = this.config.getNumbers().get(hourStr); + } else { + result.beginHour = Integer.parseInt(hourStr); + } + + hourStr = hourGroup.captures[1].value; + + if (this.config.getNumbers().containsKey(hourStr)) { + result.endHour = this.config.getNumbers().get(hourStr); + } else { + result.endHour = Integer.parseInt(hourStr); + } + + // Parse "pm" + String pmStr = match.getGroup(Constants.PmGroupName).value; + String amStr = match.getGroup(Constants.AmGroupName).value; + String descStr = match.getGroup(Constants.DescGroupName).value; + if (!StringUtility.isNullOrEmpty(amStr) || !StringUtility.isNullOrEmpty(descStr) && descStr.startsWith("a")) { + if (result.beginHour >= Constants.HalfDayHourCount) { + result.beginHour -= Constants.HalfDayHourCount; + } + + if (result.endHour >= Constants.HalfDayHourCount) { + result.endHour -= Constants.HalfDayHourCount; + } + + hasAm = true; + } else if (!StringUtility.isNullOrEmpty(pmStr) || !StringUtility.isNullOrEmpty(descStr) && descStr.startsWith("p")) { + if (result.beginHour < Constants.HalfDayHourCount) { + result.beginHour += Constants.HalfDayHourCount; + } + + if (result.endHour < Constants.HalfDayHourCount) { + result.endHour += Constants.HalfDayHourCount; + } + + hasPm = true; + } + + if (!hasAm && !hasPm && result.beginHour <= Constants.HalfDayHourCount && result.endHour <= Constants.HalfDayHourCount) { + if (result.beginHour > result.endHour) { + if (result.beginHour == Constants.HalfDayHourCount) { + result.beginHour = 0; + } else { + result.endHour += Constants.HalfDayHourCount; + } + } + + result.comments = Constants.Comment_AmPm; + } + + return result; + } + + private DateTimeResolutionResult mergeTwoTimePoints(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + DateTimeParseResult pr1; + DateTimeParseResult pr2; + boolean bothHaveDates = false; + boolean beginHasDate = false; + boolean endHasDate = false; + + List timeExtractResults = config.getTimeExtractor().extract(text, referenceDate); + List dateTimeExtractResults = config.getDateTimeExtractor().extract(text, referenceDate); + + if (dateTimeExtractResults.size() == 2) { + pr1 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + pr2 = config.getDateTimeParser().parse(dateTimeExtractResults.get(1), referenceDate); + bothHaveDates = true; + } else if (dateTimeExtractResults.size() == 1 && timeExtractResults.size() == 2) { + if (!dateTimeExtractResults.get(0).isOverlap(timeExtractResults.get(0))) { + pr1 = config.getTimeParser().parse(timeExtractResults.get(0), referenceDate); + pr2 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + endHasDate = true; + } else { + pr1 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + pr2 = config.getTimeParser().parse(timeExtractResults.get(1), referenceDate); + beginHasDate = true; + } + } else if (dateTimeExtractResults.size() == 1 && timeExtractResults.size() == 1) { + if (timeExtractResults.get(0).getStart() < dateTimeExtractResults.get(0).getStart()) { + pr1 = config.getTimeParser().parse(timeExtractResults.get(0), referenceDate); + pr2 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + endHasDate = true; + } else if (timeExtractResults.get(0).getStart() >= dateTimeExtractResults.get(0).getStart() + dateTimeExtractResults.get(0).getLength()) { + pr1 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + pr2 = config.getTimeParser().parse(timeExtractResults.get(0), referenceDate); + beginHasDate = true; + } else { + // If the only TimeExtractResult is part of DateTimeExtractResult, then it should not be handled in this method + return result; + } + } else if (timeExtractResults.size() == 2) { + // If both ends are Time. then this is a TimePeriod, not a DateTimePeriod + return result; + } else { + return result; + } + + if (pr1.getValue() == null || pr2.getValue() == null) { + return result; + } + + LocalDateTime futureBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue(); + LocalDateTime futureEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue(); + + LocalDateTime pastBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getPastValue(); + LocalDateTime pastEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getPastValue(); + + if (bothHaveDates) { + if (futureBegin.isAfter(futureEnd)) { + futureBegin = pastBegin; + } + + if (pastEnd.isBefore(pastBegin)) { + pastEnd = futureEnd; + } + } + + if (bothHaveDates) { + result.setTimex(String.format("(%s,%s,PT%dH)", pr1.getTimexStr(), pr2.getTimexStr(), Math.round(ChronoUnit.SECONDS.between(futureBegin, futureEnd) / 3600f))); + // Do nothing + } else if (beginHasDate) { + futureEnd = DateUtil.safeCreateFromMinValue(futureBegin.toLocalDate(), futureEnd.toLocalTime()); + pastEnd = DateUtil.safeCreateFromMinValue(pastBegin.toLocalDate(), pastEnd.toLocalTime()); + + String dateStr = pr1.getTimexStr().split("T")[0]; + result.setTimex(String.format("(%s,%s,PT%dH)", pr1.getTimexStr(), dateStr + pr2.getTimexStr(), ChronoUnit.HOURS.between(futureBegin, futureEnd))); + } else if (endHasDate) { + futureBegin = DateUtil.safeCreateFromMinValue(futureEnd.getYear(), futureEnd.getMonthValue(), futureEnd.getDayOfMonth(), + futureBegin.getHour(), futureBegin.getMinute(), futureBegin.getSecond()); + + pastBegin = DateUtil.safeCreateFromMinValue(pastEnd.getYear(), pastEnd.getMonthValue(), pastEnd.getDayOfMonth(), + pastBegin.getHour(), pastBegin.getMinute(), pastBegin.getSecond()); + + + String dateStr = pr2.getTimexStr().split("T")[0]; + result.setTimex(String.format("(%s,%s,PT%dH)", dateStr + pr1.getTimexStr(), pr2.getTimexStr(), ChronoUnit.HOURS.between(futureBegin, futureEnd))); + } + + DateTimeResolutionResult pr1Value = (DateTimeResolutionResult)pr1.getValue(); + DateTimeResolutionResult pr2Value = (DateTimeResolutionResult)pr2.getValue(); + + String ampmStr1 = pr1Value.getComment(); + String ampmStr2 = pr2Value.getComment(); + + if (!StringUtility.isNullOrEmpty(ampmStr1) && ampmStr1.endsWith(Constants.Comment_AmPm) && + !StringUtility.isNullOrEmpty(ampmStr2) && ampmStr2.endsWith(Constants.Comment_AmPm)) { + result.setComment(Constants.Comment_AmPm); + } + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + if (pr1Value.getTimeZoneResolution() != null) { + result.setTimeZoneResolution(pr1Value.getTimeZoneResolution()); + } + + if (pr2Value.getTimeZoneResolution() != null) { + result.setTimeZoneResolution(pr2Value.getTimeZoneResolution()); + } + } + + result.setFutureValue(new Pair(futureBegin, futureEnd)); + result.setPastValue(new Pair(pastBegin, pastEnd)); + + result.setSuccess(true); + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(pr1); + subDateTimeEntities.add(pr2); + result.setSubDateTimeEntities(subDateTimeEntities); + + return result; + } + + // Parse specific TimeOfDay like "this night", "early morning", "late evening" + protected DateTimeResolutionResult parseSpecificTimeOfDay(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + String timeText = trimmedText; + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), trimmedText)).findFirst(); + + // Extract early/late prefix from text if any + boolean hasEarly = false; + boolean hasLate = false; + if (match.isPresent()) { + timeText = match.get().getGroup("timeOfDay").value; + + if (!StringUtility.isNullOrEmpty(match.get().getGroup("early").value)) { + hasEarly = true; + result.setComment(Constants.Comment_Early); + result.setMod(Constants.EARLY_MOD); + } + + if (!hasEarly && !StringUtility.isNullOrEmpty(match.get().getGroup("late").value)) { + hasLate = true; + result.setComment(Constants.Comment_Late); + result.setMod(Constants.LATE_MOD); + } + } else { + match = Arrays.stream(RegExpUtility.getMatches(config.getAmDescRegex(), trimmedText)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPmDescRegex(), trimmedText)).findFirst(); + } + + if (match.isPresent()) { + timeText = match.get().value; + } + } + + // Handle time of day + + String timeStr = null; + int beginHour = -1; + int endHour = -1; + int endMin = -1; + + // Late/early only works with time of day + // Only standard time of day (morinng, afternoon, evening and night) will not directly return + MatchedTimeRangeResult matchedTimeRange = config.getMatchedTimeRange(timeText, timeStr, beginHour, endHour, endMin); + timeStr = matchedTimeRange.getTimeStr(); + beginHour = matchedTimeRange.getBeginHour(); + endHour = matchedTimeRange.getEndHour(); + endMin = matchedTimeRange.getEndMin(); + + if (!matchedTimeRange.getMatched()) { + return result; + } + + // Modify time period if "early" or "late" exists + // Since 'time of day' is defined as four hour periods, + // the first 2 hours represent early, the later 2 hours represent late + if (hasEarly) { + endHour = beginHour + 2; + // Handling speical case: night ends with 23:59 + if (endMin == 59) { + endMin = 0; + } + } else if (hasLate) { + beginHour = beginHour + 2; + } + + if (RegexExtension.isExactMatch(config.getSpecificTimeOfDayRegex(), trimmedText, true)) { + int swift = config.getSwiftPrefix(trimmedText); + + LocalDateTime date = referenceDate.plusDays(swift); + int day = date.getDayOfMonth(); + int month = date.getMonthValue(); + int year = date.getYear(); + + result.setTimex(DateTimeFormatUtil.formatDate(date) + timeStr); + + Pair resultValue = new Pair( + DateUtil.safeCreateFromMinValue(year, month, day, beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(year, month, day, endHour, endMin, endMin) + ); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + + return result; + } + + // Handle Date followed by morning, afternoon and morning, afternoon followed by Date + match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), trimmedText)).findFirst(); + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getAmDescRegex(), trimmedText)).findFirst(); + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPmDescRegex(), trimmedText)).findFirst(); + } + } + + if (match.isPresent()) { + String beforeStr = trimmedText.substring(0, match.get().index).trim(); + String afterStr = trimmedText.substring(match.get().index + match.get().length).trim(); + + // Eliminate time period, if any + List timePeriodErs = config.getTimePeriodExtractor().extract(beforeStr); + if (timePeriodErs.size() > 0) { + beforeStr = beforeStr.substring(0, timePeriodErs.get(0).getStart()) + beforeStr.substring(timePeriodErs.get(0).getStart() + timePeriodErs.get(0).getLength()) + .trim(); + } else { + timePeriodErs = config.getTimePeriodExtractor().extract(afterStr); + if (timePeriodErs.size() > 0) { + afterStr = afterStr.substring(0, timePeriodErs.get(0).getStart()) + afterStr.substring(timePeriodErs.get(0).getStart() + timePeriodErs.get(0).getLength()) + .trim(); + } + } + + List ers = config.getDateExtractor().extract(beforeStr + " " + afterStr, referenceDate); + + if (ers.size() == 0 || ers.get(0).getLength() < beforeStr.length()) { + boolean valid = false; + + if (ers.size() > 0 && ers.get(0).getStart() == 0) { + String midStr = beforeStr.substring(ers.get(0).getStart() + ers.get(0).getLength()); + if (StringUtility.isNullOrWhiteSpace(midStr.replace(",", " "))) { + valid = true; + } + } + + if (!valid) { + ers = config.getDateExtractor().extract(afterStr, referenceDate); + + if (ers.size() == 0 || ers.get(0).getLength() != beforeStr.length()) { + if (ers.size() > 0 && ers.get(0).getStart() + ers.get(0).getLength() == afterStr.length()) { + String midStr = afterStr.substring(0, ers.get(0).getStart()); + if (StringUtility.isNullOrWhiteSpace(midStr.replace(",", " "))) { + valid = true; + } + } + } else { + valid = true; + } + } + + if (!valid) { + return result; + } + } + + boolean hasSpecificTimePeriod = false; + if (timePeriodErs.size() > 0) { + DateTimeParseResult timePr = config.getTimePeriodParser().parse(timePeriodErs.get(0), referenceDate); + if (timePr != null) { + Pair periodFuture = (Pair)((DateTimeResolutionResult)timePr.getValue()).getFutureValue(); + Pair periodPast = (Pair)((DateTimeResolutionResult)timePr.getValue()).getPastValue(); + + if (periodFuture == periodPast) { + beginHour = periodFuture.getValue0().getHour(); + endHour = periodFuture.getValue1().getHour(); + } else { + if (periodFuture.getValue0().getHour() >= beginHour || periodFuture.getValue1().getHour() <= endHour) { + beginHour = periodFuture.getValue0().getHour(); + endHour = periodFuture.getValue1().getHour(); + } else { + beginHour = periodPast.getValue0().getHour(); + endHour = periodPast.getValue1().getHour(); + } + } + + hasSpecificTimePeriod = true; + } + } + + DateTimeParseResult pr = config.getDateParser().parse(ers.get(0), referenceDate); + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + if (!hasSpecificTimePeriod) { + result.setTimex(pr.getTimexStr() + timeStr); + } else { + result.setTimex(String.format("(%sT%d,%sT%d,PT%dH)", pr.getTimexStr(), beginHour, pr.getTimexStr(), endHour, endHour - beginHour)); + } + + Pair futureResult = new Pair( + DateUtil.safeCreateFromMinValue( + futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue( + futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + endHour, endMin, endMin) + ); + + Pair pastResult = new Pair( + DateUtil.safeCreateFromMinValue( + pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue( + pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + endHour, endMin, endMin) + ); + + result.setFutureValue(futureResult); + result.setPastValue(pastResult); + + result.setSuccess(true); + + return result; + } + + return result; + } + + // TODO: this can be abstracted with the similar method in BaseDatePeriodParser + // Parse "in 20 minutes" + private DateTimeResolutionResult parseDuration(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + // For the rest of datetime, it will be handled in next function + if (RegExpUtility.getMatches(config.getRestOfDateTimeRegex(), text).length > 0) { + return result; + } + + List ers = config.getDurationExtractor().extract(text, referenceTime); + + if (ers.size() == 1) { + ParseResult pr = config.getDurationParser().parse(ers.get(0)); + + String beforeStr = text.substring(0, pr.getStart()).trim().toLowerCase(); + String afterStr = text.substring(pr.getStart() + pr.getLength()).trim().toLowerCase(); + + List numbersInSuffix = config.getCardinalExtractor().extract(beforeStr); + List numbersInDuration = config.getCardinalExtractor().extract(ers.get(0).getText()); + + // Handle cases like "2 upcoming days", "5 previous years" + if (!numbersInSuffix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult numberEr = numbersInSuffix.get(0); + String numberText = numberEr.getText(); + String durationText = ers.get(0).getText(); + String combinedText = String.format("%s %s", numberText, durationText); + List combinedDurationEr = config.getDurationExtractor().extract(combinedText, referenceTime); + + if (!combinedDurationEr.isEmpty()) { + pr = config.getDurationParser().parse(combinedDurationEr.get(0)); + int startIndex = numberEr.getStart() + numberEr.getLength(); + beforeStr = beforeStr.substring(startIndex).trim(); + } + } + + if (pr.getValue() != null) { + int swiftSeconds = 0; + String mod = ""; + DateTimeResolutionResult durationResult = (DateTimeResolutionResult)pr.getValue(); + + if (durationResult.getPastValue() instanceof Double && durationResult.getFutureValue() instanceof Double) { + swiftSeconds = Math.round(((Double)durationResult.getPastValue()).floatValue()); + } + + LocalDateTime beginTime = referenceTime; + LocalDateTime endTime = referenceTime; + + if (RegexExtension.isExactMatch(config.getPastRegex(), beforeStr, true)) { + mod = Constants.BEFORE_MOD; + beginTime = referenceTime.minusSeconds(swiftSeconds); + } + + // Handle the "within (the) (next) xx seconds/minutes/hours" case + // Should also handle the multiple duration case like P1DT8H + // Set the beginTime equal to reference time for now + if (RegexExtension.isExactMatch(config.getWithinNextPrefixRegex(), beforeStr, true)) { + endTime = beginTime.plusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getFutureRegex(), beforeStr, true)) { + mod = Constants.AFTER_MOD; + endTime = beginTime.plusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getPastRegex(), afterStr, true)) { + mod = Constants.BEFORE_MOD; + beginTime = referenceTime.minusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getFutureRegex(), afterStr, true)) { + mod = Constants.AFTER_MOD; + endTime = beginTime.plusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getFutureSuffixRegex(), afterStr, true)) { + mod = Constants.AFTER_MOD; + endTime = beginTime.plusSeconds(swiftSeconds); + } + + result.setTimex(String.format("(%sT%s,%sT%s,%s)", + DateTimeFormatUtil.luisDate(beginTime), + DateTimeFormatUtil.luisTime(beginTime), + DateTimeFormatUtil.luisDate(endTime), + DateTimeFormatUtil.luisTime(endTime), + durationResult.getTimex() + )); + + Pair resultValue = new Pair(beginTime, endTime); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + + if (!StringUtility.isNullOrEmpty(mod)) { + ((DateTimeResolutionResult)pr.getValue()).setMod(mod); + } + + List subDateTimeEntities = new ArrayList(); + subDateTimeEntities.add(pr); + result.setSubDateTimeEntities(subDateTimeEntities); + + return result; + } + } + + return result; + } + + // Parse "last minute", "next hour" + private DateTimeResolutionResult parseRelativeUnit(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getRelativeTimeUnitRegex(), text)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getRestOfDateTimeRegex(), text)).findFirst(); + } + + if (match.isPresent()) { + String srcUnit = match.get().getGroup("unit").value; + + String unitStr = config.getUnitMap().get(srcUnit); + + int swiftValue = 1; + Optional prefixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPastRegex(), text)).findFirst(); + if (prefixMatch.isPresent()) { + swiftValue = -1; + } + + LocalDateTime beginTime = referenceDate; + LocalDateTime endTime = referenceDate; + String ptTimex = ""; + + if (config.getUnitMap().containsKey(srcUnit)) { + switch (unitStr) { + case "D": + endTime = DateUtil.safeCreateFromMinValue(beginTime.getYear(), beginTime.getMonthValue(), beginTime.getDayOfMonth()); + endTime = endTime.plusDays(1).minusSeconds(1); + ptTimex = String.format("PT%dS", ChronoUnit.SECONDS.between(beginTime, endTime)); + break; + case "H": + beginTime = swiftValue > 0 ? beginTime : referenceDate.plusHours(swiftValue); + endTime = swiftValue > 0 ? referenceDate.plusHours(swiftValue) : endTime; + ptTimex = "PT1H"; + break; + case "M": + beginTime = swiftValue > 0 ? beginTime : referenceDate.plusMinutes(swiftValue); + endTime = swiftValue > 0 ? referenceDate.plusMinutes(swiftValue) : endTime; + ptTimex = "PT1M"; + break; + case "S": + beginTime = swiftValue > 0 ? beginTime : referenceDate.plusSeconds(swiftValue); + endTime = swiftValue > 0 ? referenceDate.plusSeconds(swiftValue) : endTime; + ptTimex = "PT1S"; + break; + default: + return result; + } + + result.setTimex(String.format("(%sT%s,%sT%s,%s)", + DateTimeFormatUtil.luisDate(beginTime), + DateTimeFormatUtil.luisTime(beginTime), + DateTimeFormatUtil.luisDate(endTime), + DateTimeFormatUtil.luisTime(endTime), + ptTimex + )); + + Pair resultValue = new Pair(beginTime, endTime); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + + return result; + } + } + + return result; + } + + private DateTimeResolutionResult parseDateWithPeriodPrefix(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + List dateResult = config.getDateExtractor().extract(text); + if (dateResult.size() > 0) { + String beforeStr = StringUtility.trimEnd(text.substring(0, dateResult.get(dateResult.size() - 1).getStart())); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPrefixDayRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + DateTimeParseResult pr = config.getDateParser().parse(dateResult.get(dateResult.size() - 1), referenceDate); + if (pr.getValue() != null) { + LocalDateTime startTime = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + startTime = LocalDateTime.of(startTime.getYear(), startTime.getMonthValue(), startTime.getDayOfMonth(), 0, 0, 0); + LocalDateTime endTime = startTime; + + if (!StringUtility.isNullOrEmpty(match.get().getGroup("EarlyPrefix").value)) { + endTime = endTime.plusHours(Constants.HalfDayHourCount); + result.setMod(Constants.EARLY_MOD); + } else if (!StringUtility.isNullOrEmpty(match.get().getGroup("MidPrefix").value)) { + startTime = startTime.plusHours(Constants.HalfDayHourCount - Constants.HalfMidDayDurationHourCount); + endTime = endTime.plusHours(Constants.HalfDayHourCount + Constants.HalfMidDayDurationHourCount); + result.setMod(Constants.MID_MOD); + } else if (!StringUtility.isNullOrEmpty(match.get().getGroup("LatePrefix").value)) { + startTime = startTime.plusHours(Constants.HalfDayHourCount); + endTime = startTime.plusHours(Constants.HalfDayHourCount); + result.setMod(Constants.LATE_MOD); + } else { + return result; + } + + result.setTimex(pr.getTimexStr()); + + Pair resultValue = new Pair(startTime, endTime); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + } + } + } + + return result; + } + + private DateTimeResolutionResult parseDateWithTimePeriodSuffix(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional dateEr = config.getDateExtractor().extract(text).stream().findFirst(); + Optional timeEr = config.getTimeExtractor().extract(text).stream().findFirst(); + + if (dateEr.isPresent() && timeEr.isPresent()) { + int dateStrEnd = dateEr.get().getStart() + dateEr.get().getLength(); + + if (dateStrEnd < timeEr.get().getStart()) { + String midStr = text.substring(dateStrEnd, timeEr.get().getStart()); + + if (isValidConnectorForDateAndTimePeriod(midStr)) { + DateTimeParseResult datePr = config.getDateParser().parse(dateEr.get(), referenceDate); + DateTimeParseResult timePr = config.getTimeParser().parse(timeEr.get(), referenceDate); + + if (datePr != null && timePr != null) { + DateTimeResolutionResult timeResolutionResult = (DateTimeResolutionResult)timePr.getValue(); + DateTimeResolutionResult dateResolutionResult = (DateTimeResolutionResult)datePr.getValue(); + LocalDateTime futureDateValue = (LocalDateTime)dateResolutionResult.getFutureValue(); + LocalDateTime pastDateValue = (LocalDateTime)dateResolutionResult.getPastValue(); + LocalDateTime futureTimeValue = (LocalDateTime)timeResolutionResult.getFutureValue(); + LocalDateTime pastTimeValue = (LocalDateTime)timeResolutionResult.getPastValue(); + + result.setComment(timeResolutionResult.getComment()); + result.setTimex(datePr.getTimexStr() + timePr.getTimexStr()); + + result.setFutureValue(DateUtil.safeCreateFromMinValue(futureDateValue.toLocalDate(), futureTimeValue.toLocalTime())); + result.setPastValue(DateUtil.safeCreateFromMinValue(pastDateValue.toLocalDate(), pastTimeValue.toLocalTime())); + + if (RegExpUtility.getMatches(config.getBeforeRegex(), midStr).length > 0) { + result.setMod(Constants.BEFORE_MOD); + } else { + result.setMod(Constants.AFTER_MOD); + } + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(datePr); + subDateTimeEntities.add(timePr); + + result.setSubDateTimeEntities(subDateTimeEntities); + + result.setSuccess(true); + } + } + } + } + + return result; + } + + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + // Valid connector in English for Before include: "before", "no later than", "in advance of", "prior to", "earlier than", "sooner than", "by", "till", "until"... + // Valid connector in English for After include: "after", "later than" + private boolean isValidConnectorForDateAndTimePeriod(String text) { + List beforeAfterRegexes = new ArrayList<>(); + beforeAfterRegexes.add(config.getBeforeRegex()); + beforeAfterRegexes.add(config.getAfterRegex()); + text = text.trim(); + + for (Pattern regex : beforeAfterRegexes) { + ConditionalMatch match = RegexExtension.matchExact(regex, text, true); + if (match.getSuccess()) { + return true; + } + } + + return false; + } + + private class ParseTimePeriodResult { + String comments; + int beginHour; + int endHour; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java new file mode 100644 index 000000000..85bb606b4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java @@ -0,0 +1,414 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseDurationParser implements IDateTimeParser { + + private final IDurationParserConfiguration config; + + public BaseDurationParser(IDurationParserConfiguration configuration) { + this.config = configuration; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DURATION; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult; + + innerResult = parseMergedDuration(er.getText(), reference); + + if (!innerResult.getSuccess()) { + innerResult = parseNumberWithUnit(er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseImplicitDuration(er.getText(), reference); + } + + if (innerResult.getSuccess()) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.DURATION, StringUtility.format((Double)innerResult.getFutureValue())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.DURATION, StringUtility.format((Double)innerResult.getPastValue())) + .build()); + + if (er.getData() != null) { + if (er.getData().equals(Constants.MORE_THAN_MOD)) { + innerResult.setMod(Constants.MORE_THAN_MOD); + } else if (er.getData().equals(Constants.LESS_THAN_MOD)) { + innerResult.setMod(Constants.LESS_THAN_MOD); + } + } + + value = innerResult; + } + } + + DateTimeParseResult result = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex() + ); + + return result; + } + + private DateTimeResolutionResult parseMergedDuration(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + IExtractor durationExtractor = config.getDurationExtractor(); + + // DurationExtractor without parameter will not extract merged duration + List ers = durationExtractor.extract(text); + + // only handle merged duration cases like "1 month 21 days" + if (ers.size() <= 1) { + result.setSuccess(false); + return result; + } + + int start = ers.get(0).getStart(); + if (start != 0) { + String beforeStr = text.substring(0, start - 1); + if (!StringUtility.isNullOrWhiteSpace(beforeStr)) { + return result; + } + } + + int end = ers.get(ers.size() - 1).getStart() + ers.get(ers.size() - 1).getLength(); + if (end != text.length()) { + String afterStr = text.substring(end); + if (!StringUtility.isNullOrWhiteSpace(afterStr)) { + return result; + } + } + + List prs = new ArrayList<>(); + Map timexMap = new HashMap<>(); + + // insert timex into a dictionary + for (ExtractResult er : ers) { + Pattern unitRegex = config.getDurationUnitRegex(); + Optional unitMatch = Arrays.stream(RegExpUtility.getMatches(unitRegex, er.getText())).findFirst(); + if (unitMatch.isPresent()) { + DateTimeParseResult pr = (DateTimeParseResult)parse(er); + if (pr.getValue() != null) { + timexMap.put(unitMatch.get().getGroup("unit").value, pr.getTimexStr()); + prs.add(pr); + } + } + } + + // sort the timex using the granularity of the duration, "P1M23D" for "1 month 23 days" and "23 days 1 month" + if (prs.size() == ers.size()) { + + result.setTimex(TimexUtility.generateCompoundDurationTimex(timexMap, config.getUnitValueMap())); + + double value = 0; + for (DateTimeParseResult pr : prs) { + value += Double.parseDouble(((DateTimeResolutionResult)pr.getValue()).getFutureValue().toString()); + } + + result.setFutureValue(value); + result.setPastValue(value); + } + + result.setSuccess(true); + return result; + } + + private DateTimeResolutionResult parseNumberWithUnit(String text, LocalDateTime reference) { + DateTimeResolutionResult result = parseNumberSpaceUnit(text); + if (!result.getSuccess()) { + result = parseNumberCombinedUnit(text); + } + + if (!result.getSuccess()) { + result = parseAnUnit(text); + } + + if (!result.getSuccess()) { + result = parseInexactNumberUnit(text); + } + + return result; + } + + // check {and} suffix after a {number} {unit} + private double parseNumberWithUnitAndSuffix(String text) { + double numVal = 0; + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSuffixAndRegex(), text)).findFirst(); + if (match.isPresent()) { + String numStr = match.get().getGroup("suffix_num").value.toLowerCase(); + + if (config.getDoubleNumbers().containsKey(numStr)) { + numVal = config.getDoubleNumbers().get(numStr); + } + } + + return numVal; + } + + private DateTimeResolutionResult parseNumberSpaceUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + // if there are spaces between nubmer and unit + List ers = config.getCardinalExtractor().extract(text); + if (ers.size() == 1) { + ExtractResult er = ers.get(0); + ParseResult pr = config.getNumberParser().parse(er); + + // followed unit: {num} (and a half hours) + String srcUnit = ""; + String noNum = text.substring(er.getStart() + er.getLength()).trim().toLowerCase(); + String suffixStr = text; + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getFollowedUnit(), noNum)).findFirst(); + if (match.isPresent()) { + srcUnit = match.get().getGroup("unit").value.toLowerCase(); + suffixStr = match.get().getGroup(Constants.SuffixGroupName).value.toLowerCase(); + } + + if (match.isPresent() && !StringUtility.isNullOrEmpty(match.get().getGroup(Constants.BusinessDayGroupName).value)) { + int numVal = Math.round(Double.valueOf(pr.getValue().toString()).floatValue()); + + String timex = TimexUtility.generateDurationTimex(numVal, Constants.TimexBusinessDay, false); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit.split(" ")[1]); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + + if (config.getUnitMap().containsKey(srcUnit)) { + double numVal = Double.parseDouble(pr.getValue().toString()) + parseNumberWithUnitAndSuffix(suffixStr); + + String unitStr = config.getUnitMap().get(srcUnit); + + String timex = TimexUtility.generateDurationTimex(numVal, unitStr, isLessThanDay(unitStr)); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + } + + return result; + } + + private DateTimeResolutionResult parseNumberCombinedUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + String suffixStr = text; + + // if there are NO spaces between number and unit + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getNumberCombinedWithUnit(), text)).findFirst(); + if (match.isPresent()) { + Double numVal = Double.parseDouble(match.get().getGroup("num").value) + parseNumberWithUnitAndSuffix(suffixStr); + String numStr = StringUtility.format(numVal); + + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + if ((numVal > 1000) && (unitStr.equals("Y") || unitStr.equals("MON") || unitStr.equals("W"))) { + return result; + } + + String timex = String.format("P%s%s%c", isLessThanDay(unitStr) ? "T" : "", numStr, unitStr.charAt(0)); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + return result; + } + } + + return result; + } + + private DateTimeResolutionResult parseAnUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + String suffixStr = text; + + // if there are NO spaces between number and unit + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getAnUnitRegex(), text)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getHalfDateUnitRegex(), text)).findFirst(); + } + + if (match.isPresent()) { + double numVal = StringUtility.isNullOrEmpty(match.get().getGroup("half").value) ? 1 : 0.5; + numVal += parseNumberWithUnitAndSuffix(suffixStr); + String numStr = StringUtility.format(numVal); + + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(TimexUtility.generateDurationTimex(numVal, unitStr, isLessThanDay(unitStr))); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + + } else if (!StringUtility.isNullOrEmpty(match.get().getGroup(Constants.BusinessDayGroupName).value)) { + String timex = TimexUtility.generateDurationTimex(numVal, Constants.TimexBusinessDay, false); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit.split(" ")[1]); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + } + + return result; + } + + private DateTimeResolutionResult parseInexactNumberUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getInexactNumberUnitRegex(), text)).findFirst(); + if (match.isPresent()) { + double numVal; + + if (!StringUtility.isNullOrEmpty(match.get().getGroup("NumTwoTerm").value)) { + numVal = 2; + } else { + // set the inexact number "few", "some" to 3 for now + numVal = 3; + } + + String numStr = StringUtility.format(numVal); + + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + String timex = String.format("P%s%s%c", isLessThanDay(unitStr) ? "T" : "", numStr, unitStr.charAt(0)); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + return result; + } + } + + return result; + } + + private DateTimeResolutionResult parseImplicitDuration(String text, LocalDateTime reference) { + // handle "all day" "all year" + DateTimeResolutionResult result = getResultFromRegex(config.getAllDateUnitRegex(), text, "1"); + + // handle "during/for the day/week/month/year" + if (config.getOptions().match(DateTimeOptions.CalendarMode) && !result.getSuccess()) { + result = getResultFromRegex(config.getDuringRegex(), text, "1"); + } + + // handle "half day", "half year" + if (!result.getSuccess()) { + result = getResultFromRegex(config.getHalfDateUnitRegex(), text, "0.5"); + } + + // handle single duration unit, it is filtered in the extraction that there is a relative word in advance + if (!result.getSuccess()) { + result = getResultFromRegex(config.getFollowedUnit(), text, "1"); + } + + return result; + } + + private DateTimeResolutionResult getResultFromRegex(Pattern pattern, String text, String numStr) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(pattern, text)).findFirst(); + if (match.isPresent()) { + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + String timex = String.format("P%s%s%c", isLessThanDay(unitStr) ? "T" : "", numStr, unitStr.charAt(0)); + double timeValue = Double.parseDouble(numStr) * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + } + + return result; + } + + private boolean isLessThanDay(String unit) { + return unit.equals("S") || unit.equals("M") || unit.equals("H"); + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java new file mode 100644 index 000000000..568ba9905 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java @@ -0,0 +1,204 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import static java.lang.Integer.parseInt; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.function.IntFunction; +import java.util.regex.Pattern; +import java.util.stream.StreamSupport; + +public class BaseHolidayParser implements IDateTimeParser { + + private final IHolidayParserConfiguration config; + + public BaseHolidayParser(IHolidayParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATE; + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + Object value = null; + + if (er.getType().equals(getParserName())) { + + DateTimeResolutionResult innerResult = parseHolidayRegexMatch(er.getText(), referenceDate); + + if (innerResult.getSuccess()) { + HashMap futureResolution = new HashMap<>(); + futureResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())); + innerResult.setFutureResolution(futureResolution); + + HashMap pastResolution = new HashMap<>(); + pastResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())); + innerResult.setPastResolution(pastResolution); + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex() + ); + + return ret; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + private DateTimeResolutionResult parseHolidayRegexMatch(String text, LocalDateTime referenceDate) { + + for (Pattern pattern : this.config.getHolidayRegexList()) { + ConditionalMatch match = RegexExtension.matchExact(pattern, text, true); + if (match.getSuccess()) { + // LUIS value string will be set in Match2Date method + DateTimeResolutionResult ret = match2Date(match.getMatch().get(), referenceDate); + + return ret; + } + } + + return new DateTimeResolutionResult(); + } + + private DateTimeResolutionResult match2Date(Match match, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String holidayStr = this.config.sanitizeHolidayToken(match.getGroup("holiday").value.toLowerCase(Locale.ROOT)); + + // get year (if exist) + String yearStr = match.getGroup("year").value.toLowerCase(); + String orderStr = match.getGroup("order").value.toLowerCase(); + int year; + boolean hasYear = false; + + if (!StringUtility.isNullOrEmpty(yearStr)) { + year = parseInt(yearStr); + hasYear = true; + } else if (!StringUtility.isNullOrEmpty(orderStr)) { + int swift = this.config.getSwiftYear((orderStr)); + if (swift < -1) { + return ret; + } + + year = referenceDate.getYear() + swift; + hasYear = true; + } else { + year = referenceDate.getYear(); + } + + String holidayKey = ""; + for (ImmutableMap.Entry> holidayPair : this.config.getHolidayNames().entrySet()) { + if (StreamSupport.stream(holidayPair.getValue().spliterator(), false).anyMatch(name -> holidayStr.equals(name))) { + holidayKey = holidayPair.getKey(); + break; + } + } + + String timexStr = ""; + if (!StringUtility.isNullOrEmpty(holidayKey)) { + + LocalDateTime value = referenceDate; + IntFunction function = this.config.getHolidayFuncDictionary().get(holidayKey); + if (function != null) { + + value = function.apply(year); + + timexStr = this.config.getVariableHolidaysTimexDictionary().get(holidayKey); + if (StringUtility.isNullOrEmpty(timexStr)) { + timexStr = String.format("-%02d-%02d", value.getMonthValue(), value.getDayOfMonth()); + } + } + + if (function == null) { + return ret; + } + + if (value.equals(DateUtil.minValue())) { + ret.setTimex(""); + ret.setPastValue(DateUtil.minValue()); + ret.setFutureValue(DateUtil.minValue()); + ret.setSuccess(true); + return ret; + } + + if (hasYear) { + ret.setTimex(String.format("%04d", year) + timexStr); + ret.setFutureValue(DateUtil.safeCreateFromMinValue(year, value.getMonthValue(), value.getDayOfMonth())); + ret.setPastValue(DateUtil.safeCreateFromMinValue(year, value.getMonthValue(), value.getDayOfMonth())); + ret.setSuccess(true); + return ret; + } + + ret.setTimex("XXXX" + timexStr); + ret.setFutureValue(getFutureValue(value, referenceDate, holidayKey)); + ret.setPastValue(getPastValue(value, referenceDate, holidayKey)); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private LocalDateTime getFutureValue(LocalDateTime value, LocalDateTime referenceDate, String holiday) { + + if (value.isBefore(referenceDate)) { + IntFunction function = this.config.getHolidayFuncDictionary().get(holiday); + if (function != null) { + return function.apply(value.getYear() + 1); + } + } + + return value; + } + + private LocalDateTime getPastValue(LocalDateTime value, LocalDateTime referenceDate, String holiday) { + + if (value.isAfter(referenceDate) || value == referenceDate) { + IntFunction function = this.config.getHolidayFuncDictionary().get(holiday); + if (function != null) { + return function.apply(value.getYear() - 1); + } + } + + return value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java new file mode 100644 index 000000000..d8a7cfc1d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java @@ -0,0 +1,163 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.HashMap; +import java.util.function.IntFunction; +import java.util.regex.Pattern; + +public abstract class BaseHolidayParserConfiguration extends BaseOptionsConfiguration implements IHolidayParserConfiguration { + + private ImmutableMap variableHolidaysTimexDictionary; + + public final ImmutableMap getVariableHolidaysTimexDictionary() { + return variableHolidaysTimexDictionary; + } + + protected final void setVariableHolidaysTimexDictionary(ImmutableMap value) { + variableHolidaysTimexDictionary = value; + } + + private ImmutableMap> holidayFuncDictionary; + + public final ImmutableMap> getHolidayFuncDictionary() { + return holidayFuncDictionary; + } + + protected final void setHolidayFuncDictionary(ImmutableMap> value) { + holidayFuncDictionary = value; + } + + private ImmutableMap> holidayNames; + + public final ImmutableMap> getHolidayNames() { + return holidayNames; + } + + protected final void setHolidayNames(ImmutableMap> value) { + holidayNames = value; + } + + private Iterable holidayRegexList; + + public final Iterable getHolidayRegexList() { + return holidayRegexList; + } + + protected final void setHolidayRegexList(Iterable value) { + holidayRegexList = value; + } + + protected BaseHolidayParserConfiguration() { + super(DateTimeOptions.None); + this.variableHolidaysTimexDictionary = BaseDateTime.VariableHolidaysTimexDictionary; + this.setHolidayFuncDictionary(ImmutableMap.copyOf(initHolidayFuncs())); + } + + protected HashMap> initHolidayFuncs() { + HashMap> holidays = new HashMap<>(); + holidays.put("labour", BaseHolidayParserConfiguration::labourDay); + holidays.put("fathers", BaseHolidayParserConfiguration::fathersDay); + holidays.put("mothers", BaseHolidayParserConfiguration::mothersDay); + holidays.put("canberra", BaseHolidayParserConfiguration::canberraDay); + holidays.put("columbus", BaseHolidayParserConfiguration::columbusDay); + holidays.put("memorial", BaseHolidayParserConfiguration::memorialDay); + holidays.put("thanksgiving", BaseHolidayParserConfiguration::thanksgivingDay); + holidays.put("thanksgivingday", BaseHolidayParserConfiguration::thanksgivingDay); + holidays.put("blackfriday", BaseHolidayParserConfiguration::blackFriday); + holidays.put("martinlutherking", BaseHolidayParserConfiguration::martinLutherKingDay); + holidays.put("washingtonsbirthday", BaseHolidayParserConfiguration::washingtonsBirthday); + + return holidays; + } + + public abstract int getSwiftYear(String text); + + public abstract String sanitizeHolidayToken(String holiday); + + // @TODO auto-generate from YAML + private static LocalDateTime canberraDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, getDay(year, 3, 0, DayOfWeek.MONDAY)); + } + + private static LocalDateTime martinLutherKingDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, getDay(year, 1, 2, DayOfWeek.MONDAY)); + } + + private static LocalDateTime washingtonsBirthday(int year) { + return DateUtil.safeCreateFromMinValue(year, 2, getDay(year, 2, 2, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime mothersDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, getDay(year, 5, 1, DayOfWeek.SUNDAY)); + } + + protected static LocalDateTime fathersDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, getDay(year, 6, 2, DayOfWeek.SUNDAY)); + } + + protected static LocalDateTime memorialDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, getLastDay(year, 5, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime labourDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 9, getDay(year, 9, 0, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime internationalWorkersDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + protected static LocalDateTime columbusDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 10, getDay(year, 10, 1, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime thanksgivingDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, getDay(year, 11, 3, DayOfWeek.THURSDAY)); + } + + protected static LocalDateTime blackFriday(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, getDay(year, 11, 3, DayOfWeek.FRIDAY)); + } + + protected static int getDay(int year, int month, int week, DayOfWeek dayOfWeek) { + + YearMonth yearMonthObject = YearMonth.of(year, month); + int daysInMonth = yearMonthObject.lengthOfMonth(); + + int weekCount = 0; + for (int day = 1; day < daysInMonth + 1; day++) { + if (DateUtil.safeCreateFromMinValue(year, month, day).getDayOfWeek() == dayOfWeek) { + weekCount++; + if (weekCount == week + 1) { + return day; + } + } + } + + throw new Error("day out of bound."); + } + + protected static int getLastDay(int year, int month, DayOfWeek dayOfWeek) { + + YearMonth yearMonthObject = YearMonth.of(year, month); + int daysInMonth = yearMonthObject.lengthOfMonth(); + + int lastDay = 0; + for (int day = 1; day < daysInMonth + 1; day++) { + if (DateUtil.safeCreateFromMinValue(year, month, day).getDayOfWeek() == dayOfWeek) { + lastDay = day; + } + } + + return lastDay; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java new file mode 100644 index 000000000..7039f98b1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java @@ -0,0 +1,834 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.ResolutionKey; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.DateTimeResolutionKey; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.MatchingUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BaseMergedDateTimeParser implements IDateTimeParser { + + private final String parserName = "datetimeV2"; + private final IMergedParserConfiguration config; + private static final String dateMinString = DateTimeFormatUtil.formatDate(DateUtil.minValue()); + private static final String dateTimeMinString = DateTimeFormatUtil.formatDateTime(DateUtil.minValue()); + //private static final Calendar Cal = DateTimeFormatInfo.InvariantInfo.Calendar; + + public BaseMergedDateTimeParser(IMergedParserConfiguration config) { + this.config = config; + } + + public String getDateMinString() { + return dateMinString; + } + + public String getDateTimeMinString() { + return dateTimeMinString; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + DateTimeParseResult pr = null; + + String originText = er.getText(); + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + String newText = MatchingUtil.preProcessTextRemoveSuperfluousWords(er.getText(), config.getSuperfluousWordMatcher()).getText(); + int newLength = er.getLength() + er.getText().length() - originText.length(); + er = new ExtractResult(er.getStart(), newLength, newText, er.getType(), er.getData(), er.getMetadata()); + } + + // Push, save the MOD string + boolean hasBefore = false; + boolean hasAfter = false; + boolean hasSince = false; + boolean hasAround = false; + boolean hasYearAfter = false; + + // "InclusiveModifier" means MOD should include the start/end time + // For example, cases like "on or later than", "earlier than or in" have inclusive modifier + boolean hasInclusiveModifier = false; + String modStr = ""; + + if (er.getMetadata() != null && er.getMetadata().getHasMod()) { + ConditionalMatch beforeMatch = RegexExtension.matchBegin(config.getBeforeRegex(), er.getText(), true); + ConditionalMatch afterMatch = RegexExtension.matchBegin(config.getAfterRegex(), er.getText(), true); + ConditionalMatch sinceMatch = RegexExtension.matchBegin(config.getSinceRegex(), er.getText(), true); + ConditionalMatch aroundMatch = RegexExtension.matchBegin(config.getAroundRegex(), er.getText(), true); + + if (beforeMatch.getSuccess()) { + hasBefore = true; + er.setStart(er.getStart() + beforeMatch.getMatch().get().length); + er.setLength(er.getLength() - beforeMatch.getMatch().get().length); + er.setText(er.getText().substring(beforeMatch.getMatch().get().length)); + modStr = beforeMatch.getMatch().get().value; + + if (!StringUtility.isNullOrEmpty(beforeMatch.getMatch().get().getGroup("include").value)) { + hasInclusiveModifier = true; + } + } else if (afterMatch.getSuccess()) { + hasAfter = true; + er.setStart(er.getStart() + afterMatch.getMatch().get().length); + er.setLength(er.getLength() - afterMatch.getMatch().get().length); + er.setText(er.getText().substring(afterMatch.getMatch().get().length)); + modStr = afterMatch.getMatch().get().value; + + if (!StringUtility.isNullOrEmpty(afterMatch.getMatch().get().getGroup("include").value)) { + hasInclusiveModifier = true; + } + } else if (sinceMatch.getSuccess()) { + hasSince = true; + er.setStart(er.getStart() + sinceMatch.getMatch().get().length); + er.setLength(er.getLength() - sinceMatch.getMatch().get().length); + er.setText(er.getText().substring(sinceMatch.getMatch().get().length)); + modStr = sinceMatch.getMatch().get().value; + } else if (aroundMatch.getSuccess()) { + hasAround = true; + er.setStart(er.getStart() + aroundMatch.getMatch().get().length); + er.setLength(er.getLength() - aroundMatch.getMatch().get().length); + er.setText(er.getText().substring(aroundMatch.getMatch().get().length)); + modStr = aroundMatch.getMatch().get().value; + } else if ((er.getType().equals(Constants.SYS_DATETIME_DATEPERIOD) && + Arrays.stream(RegExpUtility.getMatches(config.getYearRegex(), er.getText())).findFirst().isPresent()) || + (er.getType().equals(Constants.SYS_DATETIME_DATE)) || (er.getType().equals(Constants.SYS_DATETIME_TIME))) { + // This has to be put at the end of the if, or cases like "before 2012" and "after 2012" would fall into this + // 2012 or after/above, 3 pm or later + ConditionalMatch match = RegexExtension.matchEnd(config.getSuffixAfterRegex(), er.getText(), true); + if (match.getSuccess()) { + hasYearAfter = true; + er.setLength(er.getLength() - match.getMatch().get().length); + er.setText(er.getLength() > 0 ? er.getText().substring(0, er.getLength()) : ""); + modStr = match.getMatch().get().value; + } + } + } + + if (er.getType().equals(Constants.SYS_DATETIME_DATE)) { + if (er.getMetadata() != null && er.getMetadata().getIsHoliday()) { + pr = config.getHolidayParser().parse(er, reference); + } else { + pr = this.config.getDateParser().parse(er, reference); + } + } else if (er.getType().equals(Constants.SYS_DATETIME_TIME)) { + pr = this.config.getTimeParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATETIME)) { + pr = this.config.getDateTimeParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATEPERIOD)) { + pr = this.config.getDatePeriodParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + pr = this.config.getTimePeriodParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + pr = this.config.getDateTimePeriodParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DURATION)) { + pr = this.config.getDurationParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_SET)) { + pr = this.config.getGetParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATETIMEALT)) { + pr = this.config.getDateTimeAltParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_TIMEZONE)) { + if (config.getOptions().match(DateTimeOptions.EnablePreview)) { + pr = this.config.getTimeZoneParser().parse(er, reference); + } + } else { + return null; + } + + if (pr == null) { + return null; + } + + // Pop, restore the MOD string + if (hasBefore && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + + if (!hasInclusiveModifier) { + val.setMod(combineMod(val.getMod(), Constants.BEFORE_MOD)); + } else { + val.setMod(combineMod(val.getMod(), Constants.UNTIL_MOD)); + } + + pr.setValue(val); + } + + if (hasAfter && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + + if (!hasInclusiveModifier) { + val.setMod(combineMod(val.getMod(), Constants.AFTER_MOD)); + } else { + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + } + + pr.setValue(val); + } + + if (hasSince && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + pr.setValue(val); + } + + if (hasAround && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.APPROX_MOD)); + pr.setValue(val); + } + + if (hasYearAfter && pr != null && pr.getValue() != null) { + + pr.setText(pr.getText() + modStr); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + pr.setValue(val); + hasSince = true; + } + + // For cases like "3 pm or later on Monday" + if (pr != null && pr.getValue() != null && pr.getType().equals(Constants.SYS_DATETIME_DATETIME)) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSuffixAfterRegex(), pr.getText())).findFirst(); + if (match.isPresent() && match.get().index != 0) { + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + pr.setValue(val); + hasSince = true; + } + } + + if (config.getOptions().match(DateTimeOptions.SplitDateAndTime) && pr != null && pr.getValue() != null && + ((DateTimeResolutionResult)pr.getValue()).getSubDateTimeEntities() != null) { + pr.setValue(dateTimeResolutionForSplit(pr)); + } else { + boolean hasModifier = hasBefore || hasAfter || hasSince; + if (pr.getValue() != null) { + ((DateTimeResolutionResult)pr.getValue()).setHasRangeChangingMod(hasModifier); + } + + pr = setParseResult(pr, hasModifier); + } + + // In this version, ExperimentalMode only cope with the "IncludePeriodEnd" case + if (this.config.getOptions().match(DateTimeOptions.ExperimentalMode)) { + if (pr.getMetadata() != null && pr.getMetadata().getIsPossiblyIncludePeriodEnd()) { + pr = setInclusivePeriodEnd(pr); + } + } + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + int prLength = pr.getLength() + originText.length() - pr.getText().length(); + pr = new DateTimeParseResult(pr.getStart(), prLength, originText, pr.getType(), pr.getData(), pr.getValue(), pr.getResolutionStr(), pr.getTimexStr()); + } + + return pr; + } + + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + private boolean filterResultsPredicate(DateTimeParseResult pr, Match match) { + return !(match.index < pr.getStart() + pr.getLength() && pr.getStart() < match.index + match.length); + } + + public DateTimeParseResult setParseResult(DateTimeParseResult slot, boolean hasMod) { + SortedMap slotValue = dateTimeResolution(slot); + // Change the type at last for the after or before modes + String type = String.format("%s.%s", parserName, determineDateTimeType(slot.getType(), hasMod)); + + slot.setValue(slotValue); + slot.setType(type); + + return slot; + } + + public DateTimeParseResult setInclusivePeriodEnd(DateTimeParseResult slot) { + String currentType = parserName + "." + Constants.SYS_DATETIME_DATEPERIOD; + if (slot.getType().equals(currentType)) { + Stream timexStream = Arrays.asList(slot.getTimexStr().split(",|\\(|\\)")).stream(); + String[] timexComponents = timexStream.filter(str -> !str.isEmpty()).collect(Collectors.toList()).toArray(new String[0]); + + // Only handle DatePeriod like "(StartDate,EndDate,Duration)" + if (timexComponents.length == 3) { + TreeMap value = (TreeMap)slot.getValue(); + String altTimex = ""; + + if (value != null && value.containsKey(ResolutionKey.ValueSet)) { + if (value.get(ResolutionKey.ValueSet) instanceof List) { + List> valueSet = (List>)value.get(ResolutionKey.ValueSet); + if (!value.isEmpty()) { + + for (HashMap values : valueSet) { + // This is only a sanity check, as here we only handle DatePeriod like "(StartDate,EndDate,Duration)" + if (values.containsKey(DateTimeResolutionKey.START) && + values.containsKey(DateTimeResolutionKey.END) && + values.containsKey(DateTimeResolutionKey.Timex)) { + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDateTime startDate = LocalDate.parse(values.get(DateTimeResolutionKey.START), formatter).atStartOfDay(); + LocalDateTime endDate = LocalDate.parse(values.get(DateTimeResolutionKey.END), formatter).atStartOfDay(); + String durationStr = timexComponents[2]; + DatePeriodTimexType datePeriodTimexType = TimexUtility.getDatePeriodTimexType(durationStr); + + endDate = TimexUtility.offsetDateObject(endDate, 1, datePeriodTimexType); + values.put(DateTimeResolutionKey.END, DateTimeFormatUtil.luisDate(endDate)); + values.put(DateTimeResolutionKey.Timex, generateEndInclusiveTimex(slot.getTimexStr(), datePeriodTimexType, startDate, endDate)); + + if (StringUtility.isNullOrEmpty(altTimex)) { + altTimex = values.get(DateTimeResolutionKey.Timex); + } + } + } + } + } + } + + slot.setValue(value); + slot.setTimexStr(altTimex); + } + } + return slot; + } + + public String generateEndInclusiveTimex(String originalTimex, DatePeriodTimexType datePeriodTimexType, LocalDateTime startDate, LocalDateTime endDate) { + String timexEndInclusive = TimexUtility.generateDatePeriodTimex(startDate, endDate, datePeriodTimexType); + + // Sometimes the original timex contains fuzzy part like "XXXX-05-31" + // The fuzzy part needs to stay the same in the new end-inclusive timex + if (originalTimex.contains(Character.toString(Constants.TimexFuzzy)) && originalTimex.length() == timexEndInclusive.length()) { + char[] timexCharSet = new char[timexEndInclusive.length()]; + + for (int i = 0; i < originalTimex.length(); i++) { + if (originalTimex.charAt(i) != Constants.TimexFuzzy) { + timexCharSet[i] = timexEndInclusive.charAt(i); + } else { + timexCharSet[i] = Constants.TimexFuzzy; + } + } + + timexEndInclusive = new String(timexCharSet); + } + + return timexEndInclusive; + } + + public String determineDateTimeType(String type, boolean hasMod) { + if (config.getOptions().match(DateTimeOptions.SplitDateAndTime)) { + if (type.equals(Constants.SYS_DATETIME_DATETIME)) { + return Constants.SYS_DATETIME_TIME; + } + } else { + if (hasMod) { + if (type.equals(Constants.SYS_DATETIME_DATE)) { + return Constants.SYS_DATETIME_DATEPERIOD; + } + + if (type.equals(Constants.SYS_DATETIME_TIME)) { + return Constants.SYS_DATETIME_TIMEPERIOD; + } + + if (type.equals(Constants.SYS_DATETIME_DATETIME)) { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + } + } + + return type; + } + + public String determineSourceEntityType(String sourceType, String newType, boolean hasMod) { + if (!hasMod) { + return null; + } + + if (!newType.equals(sourceType)) { + return Constants.SYS_DATETIME_DATETIMEPOINT; + } + + if (newType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + + return null; + } + + public List dateTimeResolutionForSplit(DateTimeParseResult slot) { + List results = new ArrayList<>(); + if (((DateTimeResolutionResult)slot.getValue()).getSubDateTimeEntities() != null) { + List subEntities = ((DateTimeResolutionResult)slot.getValue()).getSubDateTimeEntities(); + for (Object subEntity : subEntities) { + DateTimeParseResult result = (DateTimeParseResult)subEntity; + result.setStart(result.getStart() + slot.getStart()); + results.addAll(dateTimeResolutionForSplit(result)); + } + } else { + slot.setValue(dateTimeResolution(slot)); + slot.setType(String.format("%s.%s",parserName, determineDateTimeType(slot.getType(), false))); + + results.add(slot); + } + + return results; + } + + public SortedMap dateTimeResolution(DateTimeParseResult slot) { + if (slot == null) { + return null; + } + + List> resolutions = new ArrayList<>(); + Map res = new HashMap<>(); + + DateTimeResolutionResult val = (DateTimeResolutionResult)slot.getValue(); + if (val == null) { + return null; + } + + boolean islunar = val.getIsLunar() != null ? val.getIsLunar() : false; + String mod = val.getMod(); + + String list = null; + + // Resolve dates list for date periods + if (slot.getType().equals(Constants.SYS_DATETIME_DATEPERIOD) && val.getList() != null) { + list = String.join(",", val.getList().stream().map(o -> DateTimeFormatUtil.luisDate((LocalDateTime)o)).collect(Collectors.toList())); + } + + // With modifier, output Type might not be the same with type in resolution comments + // For example, if the resolution type is "date", with modifier the output type should be "daterange" + String typeOutput = determineDateTimeType(slot.getType(), !StringUtility.isNullOrEmpty(mod)); + String sourceEntity = determineSourceEntityType(slot.getType(), typeOutput, val.getHasRangeChangingMod()); + String comment = val.getComment(); + + String type = slot.getType(); + String timex = slot.getTimexStr(); + + // The following should be added to res first, since ResolveAmPm requires these fields. + addResolutionFields(res, DateTimeResolutionKey.Timex, timex); + addResolutionFields(res, Constants.Comment, comment); + addResolutionFields(res, DateTimeResolutionKey.Mod, mod); + addResolutionFields(res, ResolutionKey.Type, typeOutput); + addResolutionFields(res, DateTimeResolutionKey.IsLunar, islunar ? Boolean.toString(islunar) : ""); + + boolean hasTimeZone = false; + + // For standalone timezone entity recognition, we generate TimeZoneResolution for each entity we extracted. + // We also merge time entity with timezone entity and add the information in TimeZoneResolution to every DateTime resolutions. + if (val.getTimeZoneResolution() != null) { + if (slot.getType().equals(Constants.SYS_DATETIME_TIMEZONE)) { + // single timezone + Map resolutionField = new LinkedHashMap<>(); + resolutionField.put(ResolutionKey.Value, val.getTimeZoneResolution().getValue()); + resolutionField.put(Constants.UtcOffsetMinsKey, val.getTimeZoneResolution().getUtcOffsetMins().toString()); + + addResolutionFields(res, Constants.ResolveTimeZone, resolutionField); + } else { + // timezone as clarification of datetime + hasTimeZone = true; + addResolutionFields(res, Constants.TimeZone, val.getTimeZoneResolution().getValue()); + addResolutionFields(res, Constants.TimeZoneText, val.getTimeZoneResolution().getTimeZoneText()); + addResolutionFields(res, Constants.UtcOffsetMinsKey, val.getTimeZoneResolution().getUtcOffsetMins().toString()); + } + } + + LinkedHashMap pastResolutionStr = new LinkedHashMap<>(); + if (((DateTimeResolutionResult)slot.getValue()).getPastResolution() != null) { + pastResolutionStr.putAll(((DateTimeResolutionResult)slot.getValue()).getPastResolution()); + } + + Map futureResolutionStr = ((DateTimeResolutionResult)slot.getValue()).getFutureResolution(); + + if (typeOutput.equals(Constants.SYS_DATETIME_DATETIMEALT) && pastResolutionStr.size() > 0) { + typeOutput = determineResolutionDateTimeType(pastResolutionStr); + } + + Map resolutionPast = generateResolution(type, pastResolutionStr, mod); + Map resolutionFuture = generateResolution(type, futureResolutionStr, mod); + + // If past and future are same, keep only one + if (resolutionFuture.equals(resolutionPast)) { + if (resolutionPast.size() > 0) { + addResolutionFields(res, Constants.Resolve, resolutionPast); + } + } else { + if (resolutionPast.size() > 0) { + addResolutionFields(res, Constants.ResolveToPast, resolutionPast); + } + + if (resolutionFuture.size() > 0) { + addResolutionFields(res, Constants.ResolveToFuture, resolutionFuture); + } + } + + // If 'ampm', double our resolution accordingly + if (!StringUtility.isNullOrEmpty(comment) && comment.equals(Constants.Comment_AmPm)) { + if (res.containsKey(Constants.Resolve)) { + resolveAmPm(res, Constants.Resolve); + } else { + resolveAmPm(res, Constants.ResolveToPast); + resolveAmPm(res, Constants.ResolveToFuture); + } + } + + // If WeekOf and in CalendarMode, modify the past part of our resolution + if (config.getOptions().match(DateTimeOptions.CalendarMode) && + !StringUtility.isNullOrEmpty(comment) && comment.equals(Constants.Comment_WeekOf)) { + resolveWeekOf(res, Constants.ResolveToPast); + } + + if (comment != null && !comment.isEmpty() && TimexUtility.hasDoubleTimex(comment)) { + res = TimexUtility.processDoubleTimex(res, Constants.ResolveToFuture, Constants.ResolveToPast, timex); + } + + for (Map.Entry p : res.entrySet()) { + if (p.getValue() instanceof Map) { + Map value = new LinkedHashMap<>(); + + addResolutionFields(value, DateTimeResolutionKey.Timex, timex); + addResolutionFields(value, DateTimeResolutionKey.Mod, mod); + addResolutionFields(value, ResolutionKey.Type, typeOutput); + addResolutionFields(value, DateTimeResolutionKey.IsLunar, islunar ? Boolean.toString(islunar) : ""); + addResolutionFields(value, DateTimeResolutionKey.List, list); + addResolutionFields(value, DateTimeResolutionKey.SourceEntity, sourceEntity); + + if (hasTimeZone) { + addResolutionFields(value, Constants.TimeZone, val.getTimeZoneResolution().getValue()); + addResolutionFields(value, Constants.TimeZoneText, val.getTimeZoneResolution().getTimeZoneText()); + addResolutionFields(value, Constants.UtcOffsetMinsKey, val.getTimeZoneResolution().getUtcOffsetMins().toString()); + } + + for (Map.Entry q : ((Map)p.getValue()).entrySet()) { + value.put(q.getKey(), q.getValue()); + } + + resolutions.add(value); + } + } + + if (resolutionPast.size() == 0 && resolutionFuture.size() == 0 && val.getTimeZoneResolution() == null) { + Map notResolved = new LinkedHashMap<>(); + notResolved.put(DateTimeResolutionKey.Timex, timex); + notResolved.put(ResolutionKey.Type, typeOutput); + notResolved.put(ResolutionKey.Value, "not resolved"); + + resolutions.add(notResolved); + } + + SortedMap result = new TreeMap<>(); + result.put(ResolutionKey.ValueSet, resolutions); + + return result; + } + + private String combineMod(String originalMod, String newMod) { + String combinedMod = newMod; + if (originalMod != null && originalMod != "") { + combinedMod = newMod + "-" + originalMod; + } + return combinedMod; + } + + private String determineResolutionDateTimeType(LinkedHashMap pastResolutionStr) { + switch (pastResolutionStr.keySet().stream().findFirst().get()) { + case TimeTypeConstants.START_DATE: + return Constants.SYS_DATETIME_DATEPERIOD; + case TimeTypeConstants.START_DATETIME: + return Constants.SYS_DATETIME_DATETIMEPERIOD; + case TimeTypeConstants.START_TIME: + return Constants.SYS_DATETIME_TIMEPERIOD; + default: + return pastResolutionStr.keySet().stream().findFirst().get().toLowerCase(); + } + } + + private void addResolutionFields(Map dic, String key, Object value) { + if (value != null) { + dic.put(key, value); + } + } + + private void addResolutionFields(Map dic, String key, String value) { + if (!StringUtility.isNullOrEmpty(value)) { + dic.put(key, value); + } + } + + private void resolveAmPm(Map resolutionDic, String keyName) { + if (resolutionDic.containsKey(keyName)) { + Map resolution = (Map)resolutionDic.get(keyName); + + Map resolutionPm = new LinkedHashMap<>(); + + if (!resolutionDic.containsKey(DateTimeResolutionKey.Timex)) { + return; + } + + String timex = (String)resolutionDic.get(DateTimeResolutionKey.Timex); + timex = timex != null ? timex : ""; + + resolutionDic.remove(keyName); + resolutionDic.put(keyName + "Am", resolution); + + switch ((String)resolutionDic.get(ResolutionKey.Type)) { + case Constants.SYS_DATETIME_TIME: + resolutionPm.put(ResolutionKey.Value, DateTimeFormatUtil.toPm(resolution.get(ResolutionKey.Value))); + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.toPm(timex)); + break; + case Constants.SYS_DATETIME_DATETIME: + String[] splited = resolution.get(ResolutionKey.Value).split(" "); + resolutionPm.put(ResolutionKey.Value, splited[0] + " " + DateTimeFormatUtil.toPm(splited[1])); + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.allStringToPm(timex)); + break; + case Constants.SYS_DATETIME_TIMEPERIOD: + if (resolution.containsKey(DateTimeResolutionKey.START)) { + resolutionPm.put(DateTimeResolutionKey.START, DateTimeFormatUtil.toPm(resolution.get(DateTimeResolutionKey.START))); + } + + if (resolution.containsKey(DateTimeResolutionKey.END)) { + resolutionPm.put(DateTimeResolutionKey.END, DateTimeFormatUtil.toPm(resolution.get(DateTimeResolutionKey.END))); + } + + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.allStringToPm(timex)); + break; + case Constants.SYS_DATETIME_DATETIMEPERIOD: + if (resolution.containsKey(DateTimeResolutionKey.START)) { + LocalDateTime start = LocalDateTime.parse(resolution.get(DateTimeResolutionKey.START), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + start = start.getHour() == Constants.HalfDayHourCount ? start.minusHours(Constants.HalfDayHourCount) : start.plusHours(Constants.HalfDayHourCount); + + resolutionPm.put(DateTimeResolutionKey.START, DateTimeFormatUtil.formatDateTime(start)); + } + + if (resolution.containsKey(DateTimeResolutionKey.END)) { + LocalDateTime end = LocalDateTime.parse(resolution.get(DateTimeResolutionKey.END), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + end = end.getHour() == Constants.HalfDayHourCount ? end.minusHours(Constants.HalfDayHourCount) : end.plusHours(Constants.HalfDayHourCount); + + resolutionPm.put(DateTimeResolutionKey.END, DateTimeFormatUtil.formatDateTime(end)); + } + + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.allStringToPm(timex)); + break; + default: + break; + } + resolutionDic.put(keyName + "Pm", resolutionPm); + } + } + + private void resolveWeekOf(Map resolutionDic, String keyName) { + if (resolutionDic.containsKey(keyName)) { + Map resolution = (Map)resolutionDic.get(keyName); + + LocalDateTime monday = DateUtil.tryParse(resolution.get(DateTimeResolutionKey.START)); + resolution.put(DateTimeResolutionKey.Timex, TimexUtility.generateWeekTimex(monday)); + + resolutionDic.put(keyName, resolution); + } + } + + private Map generateResolution(String type, Map resolutionDic, String mod) { + Map res = new LinkedHashMap<>(); + + if (type.equals(Constants.SYS_DATETIME_DATETIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATETIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_TIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.TIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATE)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATE, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DURATION)) { + if (resolutionDic.containsKey(TimeTypeConstants.DURATION)) { + res.put(ResolutionKey.Value, resolutionDic.get(TimeTypeConstants.DURATION)); + } + } else if (type.equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_TIME, TimeTypeConstants.END_TIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATE, TimeTypeConstants.END_DATE, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATETIME, TimeTypeConstants.END_DATETIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATETIMEALT)) { + // for a period + if (resolutionDic.size() > 2) { + addAltPeriodToResolution(resolutionDic, mod, res); + } else { + // for a datetime point + addAltSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATETIMEALT, mod, res); + } + } + + return res; + } + + public void addAltPeriodToResolution(Map resolutionDic, String mod, Map res) { + if (resolutionDic.containsKey(TimeTypeConstants.START_DATETIME) && resolutionDic.containsKey(TimeTypeConstants.END_DATETIME)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATETIME, TimeTypeConstants.END_DATETIME, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.START_DATE) && resolutionDic.containsKey(TimeTypeConstants.END_DATE)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATE, TimeTypeConstants.END_DATE, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.START_TIME) && resolutionDic.containsKey(TimeTypeConstants.END_TIME)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_TIME, TimeTypeConstants.END_TIME, mod, res); + } + } + + public void addAltSingleDateTimeToResolution(Map resolutionDic, String type, String mod, Map res) { + if (resolutionDic.containsKey(TimeTypeConstants.DATE)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATE, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.DATETIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATETIME, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.TIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.TIME, mod, res); + } + } + + public void addSingleDateTimeToResolution(Map resolutionDic, String type, String mod, Map res) { + // If an "invalid" Date or DateTime is extracted, it should not have an assigned resolution. + // Only valid entities should pass this condition. + if (resolutionDic.containsKey(type) && !resolutionDic.get(type).startsWith(dateMinString)) { + if (!StringUtility.isNullOrEmpty(mod)) { + if (mod.equals(Constants.BEFORE_MOD)) { + res.put(DateTimeResolutionKey.END, resolutionDic.get(type)); + return; + } + + if (mod.equals(Constants.AFTER_MOD)) { + res.put(DateTimeResolutionKey.START, resolutionDic.get(type)); + return; + } + + if (mod.equals(Constants.SINCE_MOD)) { + res.put(DateTimeResolutionKey.START, resolutionDic.get(type)); + return; + } + + if (mod.equals(Constants.UNTIL_MOD)) { + res.put(DateTimeResolutionKey.END, resolutionDic.get(type)); + return; + } + } + + res.put(ResolutionKey.Value, resolutionDic.get(type)); + } + } + + public void addPeriodToResolution(Map resolutionDic, String startType, String endType, String mod, Map res) { + String start = ""; + String end = ""; + + if (resolutionDic.containsKey(startType)) { + if (resolutionDic.get(startType).startsWith(dateMinString)) { + return; + } + start = resolutionDic.get(startType); + } + + if (resolutionDic.containsKey(endType)) { + if (resolutionDic.get(endType).startsWith(dateMinString)) { + return; + } + end = resolutionDic.get(endType); + } + + if (!StringUtility.isNullOrEmpty(mod)) { + // For the 'before' mod + // 1. Cases like "Before December", the start of the period should be the end of the new period, not the start + // 2. Cases like "More than 3 days before today", the date point should be the end of the new period + if (mod.startsWith(Constants.BEFORE_MOD)) { + if (!StringUtility.isNullOrEmpty(start) && !StringUtility.isNullOrEmpty(end) && !mod.endsWith(Constants.LATE_MOD)) { + res.put(DateTimeResolutionKey.END, start); + } else { + res.put(DateTimeResolutionKey.END, end); + } + + return; + } + + // For the 'after' mod + // 1. Cases like "After January", the end of the period should be the start of the new period, not the end + // 2. Cases like "More than 3 days after today", the date point should be the start of the new period + if (mod.startsWith(Constants.AFTER_MOD)) { + if (!StringUtility.isNullOrEmpty(start) && !StringUtility.isNullOrEmpty(end) && !mod.endsWith(Constants.EARLY_MOD)) { + res.put(DateTimeResolutionKey.START, end); + } else { + res.put(DateTimeResolutionKey.START, start); + } + + return; + } + + // For the 'since' mod, the start of the period should be the start of the new period, not the end + if (mod.equals(Constants.SINCE_MOD)) { + res.put(DateTimeResolutionKey.START, start); + return; + } + + // For the 'until' mod, the end of the period should be the end of the new period, not the start + if (mod.equals(Constants.UNTIL_MOD)) { + res.put(DateTimeResolutionKey.END, end); + return; + } + } + + if (!StringUtility.isNullOrEmpty(start) && !StringUtility.isNullOrEmpty(end)) { + res.put(DateTimeResolutionKey.START, start); + res.put(DateTimeResolutionKey.END, end); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java new file mode 100644 index 000000000..220773230 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java @@ -0,0 +1,251 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; + +public class BaseSetParser implements IDateTimeParser { + @Override + public String getParserName() { + return Constants.SYS_DATETIME_SET; + } + + private ISetParserConfiguration config; + + public BaseSetParser(ISetParserConfiguration configuration) { + this.config = configuration; + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + DateTimeResolutionResult value = null; + + if (er.getType().equals(getParserName())) { + + DateTimeResolutionResult innerResult = parseEachUnit(er.getText()); + if (!innerResult.getSuccess()) { + innerResult = parseEachDuration(er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parserTimeEveryday(er.getText(), reference); + } + + // NOTE: Please do not change the order of following function + // datetimeperiod>dateperiod>timeperiod>datetime>date>time + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDateTimePeriodExtractor(), config.getDateTimePeriodParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDatePeriodExtractor(), config.getDatePeriodParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getTimePeriodExtractor(), config.getTimePeriodParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDateTimeExtractor(), config.getDateTimeParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDateExtractor(), config.getDateParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getTimeExtractor(), config.getTimeParser(), er.getText(), reference); + } + + if (innerResult.getSuccess()) { + HashMap futureMap = new HashMap<>(); + futureMap.put(TimeTypeConstants.SET, innerResult.getFutureValue().toString()); + innerResult.setFutureResolution(futureMap); + + HashMap pastMap = new HashMap<>(); + pastMap.put(TimeTypeConstants.SET, innerResult.getPastValue().toString()); + innerResult.setPastResolution(pastMap); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : value.getTimex() + ); + + return ret; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + private DateTimeResolutionResult parseEachDuration(String text, LocalDateTime refDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List ers = this.config.getDurationExtractor().extract(text, refDate); + if (ers.size() != 1 || !StringUtility.isNullOrWhiteSpace(text.substring(ers.get(0).getStart() + ers.get(0).getLength()))) { + return ret; + } + + String beforeStr = text.substring(0, ers.get(0).getStart()); + Matcher regexMatch = this.config.getEachPrefixRegex().matcher(beforeStr); + if (regexMatch.find()) { + DateTimeParseResult pr = this.config.getDurationParser().parse(ers.get(0), LocalDateTime.now()); + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue("Set: " + pr.getTimexStr()); + ret.setPastValue("Set: " + pr.getTimexStr()); + ret.setSuccess(true); + return ret; + } + + return ret; + } + + private DateTimeResolutionResult parseEachUnit(String text) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + // handle "daily", "weekly" + Optional matched = Arrays.stream(RegExpUtility.getMatches(this.config.getPeriodicRegex(), text)).findFirst(); + if (matched.isPresent()) { + + MatchedTimexResult result = this.config.getMatchedDailyTimex(text); + if (!result.getResult()) { + return ret; + } + + ret.setTimex(result.getTimex()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + + // Handle "each month" + ConditionalMatch exactMatch = RegexExtension.matchExact(this.config.getEachUnitRegex(), text, true); + if (exactMatch.getSuccess()) { + + String sourceUnit = exactMatch.getMatch().get().getGroup("unit").value; + if (!StringUtility.isNullOrEmpty(sourceUnit) && this.config.getUnitMap().containsKey(sourceUnit)) { + + MatchedTimexResult result = this.config.getMatchedUnitTimex(sourceUnit); + if (!result.getResult()) { + return ret; + } + + // Handle "every other month" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getEachUnitRegex(), text)).findFirst(); + + if (exactMatch.getMatch().get().getGroup("other").value != "") { + result.setTimex(result.getTimex().replace("1", "2")); + } + + ret.setTimex(result.getTimex()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + } + + return ret; + } + + private DateTimeResolutionResult parserTimeEveryday(String text, LocalDateTime refDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List ers = this.config.getTimeExtractor().extract(text, refDate); + if (ers.size() != 1) { + return ret; + } + + String afterStr = text.replace(ers.get(0).getText(), ""); + Matcher match = this.config.getEachDayRegex().matcher(afterStr); + if (match.find()) { + DateTimeParseResult pr = this.config.getTimeParser().parse(ers.get(0), LocalDateTime.now()); + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private DateTimeResolutionResult parseEach(IDateTimeExtractor extractor, IDateTimeParser parser, String text, LocalDateTime refDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List ers = null; + + // remove key words of set type from text + boolean success = false; + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSetEachRegex(), text)).findFirst(); + if (match.isPresent()) { + + StringBuilder sb = new StringBuilder(text); + String trimmedText = sb.delete(match.get().index, match.get().index + match.get().length).toString(); + ers = extractor.extract(trimmedText, refDate); + if (ers.size() == 1 && ers.get(0).getLength() == trimmedText.length()) { + + success = true; + } + } + + // remove suffix 's' and "on" if existed and re-try + match = Arrays.stream(RegExpUtility.getMatches(this.config.getSetWeekDayRegex(), text)).findFirst(); + if (match.isPresent()) { + + StringBuilder sb = new StringBuilder(text); + String trimmedText = sb.delete(match.get().index, match.get().index + match.get().length).toString(); + trimmedText = new StringBuilder(trimmedText).insert(match.get().index, match.get().getGroup("weekday").value).toString(); + ers = extractor.extract(trimmedText, refDate); + if (ers.size() == 1 && ers.get(0).getLength() == trimmedText.length()) { + + success = true; + } + } + + if (success) { + DateTimeParseResult pr = parser.parse(ers.get(0), refDate); + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java new file mode 100644 index 000000000..ce61d453f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java @@ -0,0 +1,358 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.utilities.IntegerUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseTimeParser implements IDateTimeParser { + + private final ITimeParserConfiguration config; + + public BaseTimeParser(ITimeParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_TIME; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceTime = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult; + + // Resolve timezome + if (TimeZoneUtility.shouldResolveTimeZone(er, config.getOptions())) { + Map metadata = (Map)er.getData(); + ExtractResult timezoneEr = (ExtractResult)metadata.get(Constants.SYS_DATETIME_TIMEZONE); + ParseResult timezonePr = config.getTimeZoneParser().parse(timezoneEr); + + innerResult = internalParse(er.getText().substring(0, er.getText().length() - timezoneEr.getLength()), referenceTime); + + if (timezonePr.getValue() != null) { + TimeZoneResolutionResult timeZoneResolution = ((DateTimeResolutionResult)timezonePr.getValue()).getTimeZoneResolution(); + innerResult.setTimeZoneResolution(timeZoneResolution); + } + + } else { + innerResult = internalParse(er.getText(), referenceTime); + } + + if (innerResult.getSuccess()) { + ImmutableMap.Builder futureResolution = ImmutableMap.builder(); + futureResolution.put(TimeTypeConstants.TIME, DateTimeFormatUtil.formatTime((LocalDateTime)innerResult.getFutureValue())); + + innerResult.setFutureResolution(futureResolution.build()); + + ImmutableMap.Builder pastResolution = ImmutableMap.builder(); + pastResolution.put(TimeTypeConstants.TIME, DateTimeFormatUtil.formatTime((LocalDateTime)innerResult.getPastValue())); + + innerResult.setPastResolution(pastResolution.build()); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + protected DateTimeResolutionResult internalParse(String text, LocalDateTime referenceTime) { + + DateTimeResolutionResult innerResult = parseBasicRegexMatch(text, referenceTime); + return innerResult; + } + + // parse basic patterns in TimeRegexList + private DateTimeResolutionResult parseBasicRegexMatch(String text, LocalDateTime referenceTime) { + + String trimmedText = text.trim().toLowerCase(); + int offset = 0; + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getAtRegex(), trimmedText)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getAtRegex(), config.getTimeTokenPrefix() + trimmedText)).findFirst(); + offset = config.getTimeTokenPrefix().length(); + } + + if (match.isPresent() && match.get().index == offset && match.get().length == trimmedText.length()) { + return match2Time(match.get(), referenceTime); + } + + // parse hour pattern, like "twenty one", "16" + // create a extract comments which content the pass-in text + Integer hour = null; + if (config.getNumbers().containsKey(text)) { + hour = config.getNumbers().get(text); + } else { + try { + hour = Integer.parseInt(text); + } catch (Exception ignored) { + hour = null; + } + } + + if (hour != null && hour >= 0 && hour <= 24) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + if (hour == 24) { + hour = 0; + } + + if (hour <= Constants.HalfDayHourCount && hour != 0) { + result.setComment(Constants.Comment_AmPm); + } + + result.setTimex(String.format("T%02d", hour)); + LocalDateTime value = DateUtil.safeCreateFromMinValue(referenceTime.getYear(), referenceTime.getMonthValue(), referenceTime.getDayOfMonth(), hour, 0, 0); + result.setFutureValue(value); + result.setPastValue(value); + result.setSuccess(true); + return result; + } + + for (Pattern regex : config.getTimeRegexes()) { + ConditionalMatch exactMatch = RegexExtension.matchExact(regex, trimmedText, true); + + if (exactMatch.getSuccess()) { + return match2Time(exactMatch.getMatch().get(), referenceTime); + } + } + + return new DateTimeResolutionResult(); + } + + private DateTimeResolutionResult match2Time(Match match, LocalDateTime referenceTime) { + + DateTimeResolutionResult result = new DateTimeResolutionResult(); + boolean hasMin = false; + boolean hasSec = false; + boolean hasAm = false; + boolean hasPm = false; + boolean hasMid = false; + int hour = 0; + int minute = 0; + int second = 0; + int day = referenceTime.getDayOfMonth(); + int month = referenceTime.getMonthValue(); + int year = referenceTime.getYear(); + + String writtenTimeStr = match.getGroup("writtentime").value; + + if (!StringUtility.isNullOrEmpty(writtenTimeStr)) { + // get hour + String hourStr = match.getGroup("hournum").value.toLowerCase(); + hour = config.getNumbers().get(hourStr); + + // get minute + String minStr = match.getGroup("minnum").value.toLowerCase(); + String tensStr = match.getGroup("tens").value.toLowerCase(); + + if (!StringUtility.isNullOrEmpty(minStr)) { + minute = config.getNumbers().get(minStr); + if (!StringUtility.isNullOrEmpty(tensStr)) { + minute += config.getNumbers().get(tensStr); + } + hasMin = true; + } + } else if (!StringUtility.isNullOrEmpty(match.getGroup("mid").value)) { + hasMid = true; + if (!StringUtility.isNullOrEmpty(match.getGroup("midnight").value)) { + hour = 0; + minute = 0; + second = 0; + } else if (!StringUtility.isNullOrEmpty(match.getGroup("midmorning").value)) { + hour = 10; + minute = 0; + second = 0; + } else if (!StringUtility.isNullOrEmpty(match.getGroup("midafternoon").value)) { + hour = 14; + minute = 0; + second = 0; + } else if (!StringUtility.isNullOrEmpty(match.getGroup("midday").value)) { + hour = Constants.HalfDayHourCount; + minute = 0; + second = 0; + } + } else { + // get hour + String hourStr = match.getGroup(Constants.HourGroupName).value; + if (StringUtility.isNullOrEmpty(hourStr)) { + hourStr = match.getGroup("hournum").value.toLowerCase(); + if (!config.getNumbers().containsKey(hourStr)) { + return result; + } + + hour = config.getNumbers().get(hourStr); + } else { + if (!IntegerUtility.canParse(hourStr)) { + if (!config.getNumbers().containsKey(hourStr.toLowerCase())) { + return result; + } + + hour = config.getNumbers().get(hourStr.toLowerCase()); + } else { + hour = Integer.parseInt(hourStr); + } + } + + // get minute + String minStr = match.getGroup(Constants.MinuteGroupName).value.toLowerCase(); + if (StringUtility.isNullOrEmpty(minStr)) { + minStr = match.getGroup("minnum").value; + if (!StringUtility.isNullOrEmpty(minStr)) { + minute = config.getNumbers().get(minStr); + hasMin = true; + } + + String tensStr = match.getGroup("tens").value; + if (!StringUtility.isNullOrEmpty(tensStr)) { + minute += config.getNumbers().get(tensStr); + hasMin = true; + } + } else { + minute = Integer.parseInt(minStr); + hasMin = true; + } + + // get second + String secStr = match.getGroup(Constants.SecondGroupName).value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(secStr)) { + second = Integer.parseInt(secStr); + hasSec = true; + } + } + + // Adjust by desc string + String descStr = match.getGroup(Constants.DescGroupName).value.toLowerCase(); + + // ampm is a special case in which at 6ampm = at 6 + if (isAmDesc(descStr, match)) { + if (hour >= Constants.HalfDayHourCount) { + hour -= Constants.HalfDayHourCount; + } + + if (!checkRegex(config.getUtilityConfiguration().getAmPmDescRegex(), descStr)) { + hasAm = true; + } + + } else if (isPmDesc(descStr, match)) { + if (hour < Constants.HalfDayHourCount) { + hour += Constants.HalfDayHourCount; + } + + hasPm = true; + } + + // adjust min by prefix + String timePrefix = match.getGroup(Constants.PrefixGroupName).value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(timePrefix)) { + PrefixAdjustResult prefixResult = config.adjustByPrefix(timePrefix, hour, minute, hasMin); + hour = prefixResult.hour; + minute = prefixResult.minute; + hasMin = prefixResult.hasMin; + } + + // adjust hour by suffix + String timeSuffix = match.getGroup(Constants.SuffixGroupName).value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(timeSuffix)) { + SuffixAdjustResult suffixResult = config.adjustBySuffix(timeSuffix, hour, minute, hasMin, hasAm, hasPm); + hour = suffixResult.hour; + minute = suffixResult.minute; + hasMin = suffixResult.hasMin; + hasAm = suffixResult.hasAm; + hasPm = suffixResult.hasPm; + } + + if (hour == 24) { + hour = 0; + } + + StringBuilder timex = new StringBuilder(String.format("T%02d", hour)); + + if (hasMin) { + timex.append(String.format(":%02d", minute)); + } + + if (hasSec) { + timex.append(String.format(":%02d", second)); + } + + result.setTimex(timex.toString()); + + if (hour <= Constants.HalfDayHourCount && !hasPm && !hasAm && !hasMid) { + result.setComment(Constants.Comment_AmPm); + } + + LocalDateTime resultTime = DateUtil.safeCreateFromMinValue(year, month, day, hour, minute, second); + result.setFutureValue(resultTime); + result.setPastValue(resultTime); + + result.setSuccess(true); + + return result; + } + + private boolean isAmDesc(String descStr, Match match) { + return checkRegex(config.getUtilityConfiguration().getAmDescRegex(), descStr) || + checkRegex(config.getUtilityConfiguration().getAmPmDescRegex(), descStr) || + !StringUtility.isNullOrEmpty(match.getGroup(Constants.ImplicitAmGroupName).value); + } + + private boolean isPmDesc(String descStr, Match match) { + return checkRegex(config.getUtilityConfiguration().getPmDescRegex(), descStr) || + !StringUtility.isNullOrEmpty(match.getGroup(Constants.ImplicitPmGroupName).value); + } + + private boolean checkRegex(Pattern regex, String input) { + Optional result = Arrays.stream(RegExpUtility.getMatches(regex, input)).findFirst(); + return result.isPresent(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java new file mode 100644 index 000000000..c0b3fffe6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java @@ -0,0 +1,697 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.utilities.Capture; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.javatuples.Pair; + +public class BaseTimePeriodParser implements IDateTimeParser { + + private final ITimePeriodParserConfiguration config; + + private static final String parserName = Constants.SYS_DATETIME_TIMEPERIOD; //"TimePeriod"; + + public BaseTimePeriodParser(ITimePeriodParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + Object value = null; + + if (er.getType().equals(getParserName())) { + + DateTimeResolutionResult innerResult; + + if (TimeZoneUtility.shouldResolveTimeZone(er, config.getOptions())) { + Map metadata = (HashMap)er.getData(); + + ExtractResult timezoneEr = (ExtractResult)metadata.get(Constants.SYS_DATETIME_TIMEZONE); + ParseResult timezonePr = config.getTimeZoneParser().parse(timezoneEr); + + innerResult = internalParse(er.getText().substring(0, er.getLength() - timezoneEr.getLength()), reference); + + if (timezonePr.getValue() != null) { + innerResult.setTimeZoneResolution(((DateTimeResolutionResult)timezonePr.getValue()).getTimeZoneResolution()); + } + } else { + innerResult = internalParse(er.getText(), reference); + } + + if (innerResult.getSuccess()) { + ImmutableMap.Builder futureResolution = ImmutableMap.builder(); + futureResolution.put( + TimeTypeConstants.START_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getFutureValue()).getValue0())); + futureResolution.put( + TimeTypeConstants.END_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getFutureValue()).getValue1())); + + innerResult.setFutureResolution(futureResolution.build()); + + ImmutableMap.Builder pastResolution = ImmutableMap.builder(); + pastResolution.put( + TimeTypeConstants.START_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getPastValue()).getValue0())); + pastResolution.put( + TimeTypeConstants.END_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getPastValue()).getValue1())); + + innerResult.setPastResolution(pastResolution.build()); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + private DateTimeResolutionResult internalParse(String text, LocalDateTime reference) { + DateTimeResolutionResult innerResult = parseSimpleCases(text, reference); + + if (!innerResult.getSuccess()) { + innerResult = mergeTwoTimePoints(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseTimeOfDay(text, reference); + } + + return innerResult; + } + + // Cases like "from 3 to 5am" or "between 3:30 and 5" are parsed here + private DateTimeResolutionResult parseSimpleCases(String text, LocalDateTime referenceTime) { + // Cases like "from 3 to 5pm" or "between 4 and 6am", time point is pure number without colon + DateTimeResolutionResult ret = parsePureNumCases(text, referenceTime); + + if (!ret.getSuccess()) { + // Cases like "from 3:30 to 5" or "netween 3:30am to 6pm", at least one of the time point contains colon + ret = parseSpecificTimeCases(text, referenceTime); + } + + return ret; + } + + // Cases like "from 3 to 5pm" or "between 4 and 6am", time point is pure number without colon + private DateTimeResolutionResult parsePureNumCases(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + int year = referenceTime.getYear(); + int month = referenceTime.getMonthValue(); + int day = referenceTime.getDayOfMonth(); + String trimmedText = text.trim().toLowerCase(); + + ConditionalMatch match = RegexExtension.matchBegin(this.config.getPureNumberFromToRegex(), trimmedText, true); + + if (!match.getSuccess()) { + match = RegexExtension.matchBegin(this.config.getPureNumberBetweenAndRegex(), trimmedText, true); + } + + if (match.getSuccess()) { + // this "from .. to .." pattern is valid if followed by a Date OR Constants.PmGroupName + boolean isValid = false; + + // get hours + MatchGroup hourGroup = match.getMatch().get().getGroup(Constants.HourGroupName); + String hourStr = hourGroup.captures[0].value; + int afterHourIndex = hourGroup.captures[0].index + hourGroup.captures[0].length; + + // hard to integrate this part into the regex + if (afterHourIndex == trimmedText.length() || !trimmedText.substring(afterHourIndex).trim().startsWith(":")) { + + int beginHour; + if (!this.config.getNumbers().containsKey(hourStr)) { + beginHour = Integer.parseInt(hourStr); + } else { + beginHour = this.config.getNumbers().get(hourStr); + } + + hourStr = hourGroup.captures[1].value; + afterHourIndex = hourGroup.captures[1].index + hourGroup.captures[1].length; + + if (afterHourIndex == trimmedText.length() || !trimmedText.substring(afterHourIndex).trim().startsWith(":")) { + int endHour; + if (!this.config.getNumbers().containsKey(hourStr)) { + endHour = Integer.parseInt(hourStr); + } else { + endHour = this.config.getNumbers().get(hourStr); + } + + // parse Constants.PmGroupName + String leftDesc = match.getMatch().get().getGroup("leftDesc").value; + String rightDesc = match.getMatch().get().getGroup("rightDesc").value; + String pmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + String amStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + String descStr = match.getMatch().get().getGroup(Constants.DescGroupName).value; + + // The "ampm" only occurs in time, we don't have to consider it here + if (StringUtility.isNullOrEmpty(leftDesc)) { + + boolean rightAmValid = !StringUtility.isNullOrEmpty(rightDesc) && + Arrays.stream(RegExpUtility.getMatches(config.getUtilityConfiguration().getAmDescRegex(), rightDesc.toLowerCase())).findFirst().isPresent(); + boolean rightPmValid = !StringUtility.isNullOrEmpty(rightDesc) && + Arrays.stream(RegExpUtility.getMatches(config.getUtilityConfiguration().getPmDescRegex(), rightDesc.toLowerCase())).findFirst().isPresent(); + + if (!StringUtility.isNullOrEmpty(amStr) || rightAmValid) { + if (endHour >= Constants.HalfDayHourCount) { + endHour -= Constants.HalfDayHourCount; + } + + if (beginHour >= Constants.HalfDayHourCount && beginHour - Constants.HalfDayHourCount < endHour) { + beginHour -= Constants.HalfDayHourCount; + } + + // Resolve case like "11 to 3am" + if (beginHour < Constants.HalfDayHourCount && beginHour > endHour) { + beginHour += Constants.HalfDayHourCount; + } + + isValid = true; + + } else if (!StringUtility.isNullOrEmpty(pmStr) || rightPmValid) { + + if (endHour < Constants.HalfDayHourCount) { + endHour += Constants.HalfDayHourCount; + } + + // Resolve case like "11 to 3pm" + if (beginHour + Constants.HalfDayHourCount < endHour) { + beginHour += Constants.HalfDayHourCount; + } + + isValid = true; + + } + } + + if (isValid) { + String beginStr = String.format("T%02d", beginHour); + String endStr = String.format("T%02d", endHour); + + if (endHour >= beginHour) { + ret.setTimex(String.format("(%s,%s,PT%sH)", beginStr, endStr, (endHour - beginHour))); + } else { + ret.setTimex(String.format("(%s,%s,PT%sH)", beginStr, endStr, (endHour - beginHour + 24))); + } + + // Try to get the timezone resolution + List timeErs = config.getTimeExtractor().extract(trimmedText); + for (ExtractResult er : timeErs) { + DateTimeParseResult pr = config.getTimeParser().parse(er, referenceTime); + if (((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution()); + break; + } + } + + ret.setFutureValue( + new Pair(DateUtil.safeCreateFromMinValue(year, month, day, beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(year, month, day, endHour, 0, 0))); + ret.setPastValue( + new Pair(DateUtil.safeCreateFromMinValue(year, month, day, beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(year, month, day, endHour, 0, 0))); + + ret.setSuccess(true); + } + } + } + } + + return ret; + } + + // Cases like "from 3:30 to 5" or "between 3:30am to 6pm", at least one of the time point contains colon + private DateTimeResolutionResult parseSpecificTimeCases(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + int year = referenceTime.getYear(); + int month = referenceTime.getMonthValue(); + int day = referenceTime.getDayOfMonth(); + + // Handle cases like "from 4:30 to 5" + ConditionalMatch match = RegexExtension.matchExact(config.getSpecificTimeFromToRegex(), text, true); + + if (!match.getSuccess()) { + // Handle cases like "between 5:10 and 7" + match = RegexExtension.matchExact(config.getSpecificTimeBetweenAndRegex(), text, true); + } + + if (match.getSuccess()) { + // Cases like "half past seven" are not handled here + if (!match.getMatch().get().getGroup(Constants.PrefixGroupName).value.equals("")) { + return ret; + } + + // Cases like "4" is different with "4:00" as the Timex is different "T04H" vs "T04H00M" + // Uses this invalidFlag to differentiate + int beginHour; + int beginMinute = Constants.InvalidMinute; + int beginSecond = Constants.InvalidSecond; + int endHour; + int endMinute = Constants.InvalidMinute; + int endSecond = Constants.InvalidSecond; + + // Get time1 and time2 + MatchGroup hourGroup = match.getMatch().get().getGroup(Constants.HourGroupName); + + String hourStr = hourGroup.captures[0].value; + + if (config.getNumbers().containsKey(hourStr)) { + beginHour = config.getNumbers().get(hourStr); + } else { + beginHour = Integer.parseInt(hourStr); + } + + + hourStr = hourGroup.captures[1].value; + + if (config.getNumbers().containsKey(hourStr)) { + endHour = config.getNumbers().get(hourStr); + } else { + endHour = Integer.parseInt(hourStr); + } + + int time1StartIndex = match.getMatch().get().getGroup("time1").index; + int time1EndIndex = time1StartIndex + match.getMatch().get().getGroup("time1").length; + int time2StartIndex = match.getMatch().get().getGroup("time2").index; + int time2EndIndex = time2StartIndex + match.getMatch().get().getGroup("time2").length; + + // Get beginMinute (if exists) and endMinute (if exists) + for (int i = 0; i < match.getMatch().get().getGroup(Constants.MinuteGroupName).captures.length; i++) { + Capture minuteCapture = match.getMatch().get().getGroup(Constants.MinuteGroupName).captures[i]; + if (minuteCapture.index >= time1StartIndex && (minuteCapture.index + minuteCapture.length) <= time1EndIndex) { + beginMinute = Integer.parseInt(minuteCapture.value); + } else if (minuteCapture.index >= time2StartIndex && (minuteCapture.index + minuteCapture.length) <= time2EndIndex) { + endMinute = Integer.parseInt(minuteCapture.value); + } + } + + // Get beginSecond (if exists) and endSecond (if exists) + for (int i = 0; i < match.getMatch().get().getGroup(Constants.SecondGroupName).captures.length; i++) { + Capture secondCapture = match.getMatch().get().getGroup(Constants.SecondGroupName).captures[i]; + if (secondCapture.index >= time1StartIndex && (secondCapture.index + secondCapture.length) <= time1EndIndex) { + beginSecond = Integer.parseInt(secondCapture.value); + } else if (secondCapture.index >= time2StartIndex && (secondCapture.index + secondCapture.length) <= time2EndIndex) { + endSecond = Integer.parseInt(secondCapture.value); + } + } + + // Desc here means descriptions like "am / pm / o'clock" + // Get leftDesc (if exists) and rightDesc (if exists) + String leftDesc = match.getMatch().get().getGroup("leftDesc").value; + String rightDesc = match.getMatch().get().getGroup("rightDesc").value; + + for (int i = 0; i < match.getMatch().get().getGroup(Constants.DescGroupName).captures.length; i++) { + Capture descCapture = match.getMatch().get().getGroup(Constants.DescGroupName).captures[i]; + if (descCapture.index >= time1StartIndex && (descCapture.index + descCapture.length) <= time1EndIndex && StringUtility.isNullOrEmpty(leftDesc)) { + leftDesc = descCapture.value; + } else if (descCapture.index >= time2StartIndex && (descCapture.index + descCapture.length) <= time2EndIndex && StringUtility.isNullOrEmpty(rightDesc)) { + rightDesc = descCapture.value; + } + } + + LocalDateTime beginDateTime = DateUtil.safeCreateFromMinValue( + year, + month, + day, + beginHour, + beginMinute >= 0 ? beginMinute : 0, + beginSecond >= 0 ? beginSecond : 0); + + LocalDateTime endDateTime = DateUtil.safeCreateFromMinValue( + year, + month, + day, + endHour, + endMinute >= 0 ? endMinute : 0, + endSecond >= 0 ? endSecond : 0); + + boolean hasLeftAm = !StringUtility.isNullOrEmpty(leftDesc) && leftDesc.toLowerCase().startsWith("a"); + boolean hasLeftPm = !StringUtility.isNullOrEmpty(leftDesc) && leftDesc.toLowerCase().startsWith("p"); + boolean hasRightAm = !StringUtility.isNullOrEmpty(rightDesc) && rightDesc.toLowerCase().startsWith("a"); + boolean hasRightPm = !StringUtility.isNullOrEmpty(rightDesc) && rightDesc.toLowerCase().startsWith("p"); + boolean hasLeft = hasLeftAm || hasLeftPm; + boolean hasRight = hasRightAm || hasRightPm; + + // Both timepoint has description like 'am' or 'pm' + if (hasLeft && hasRight) { + if (hasLeftAm) { + if (beginHour >= Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } + } else if (hasLeftPm) { + if (beginHour < Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.plusHours(Constants.HalfDayHourCount); + } + } + + if (hasRightAm) { + if (endHour > Constants.HalfDayHourCount) { + endDateTime = endDateTime.minusHours(Constants.HalfDayHourCount); + } + } else if (hasRightPm) { + if (endHour < Constants.HalfDayHourCount) { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } else if (hasLeft || hasRight) { // one of the timepoint has description like 'am' or 'pm' + if (hasLeftAm) { + if (beginHour >= Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } + + if (endHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } else if (hasLeftPm) { + if (beginHour < Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.plusHours(Constants.HalfDayHourCount); + } + + if (endHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + Duration span = Duration.between(endDateTime, beginDateTime).abs(); + if (span.toHours() >= Constants.HalfDayHourCount) { + endDateTime = endDateTime.plusHours(24); + } else { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } + } + + if (hasRightAm) { + if (endHour >= Constants.HalfDayHourCount) { + endDateTime = endDateTime.minusHours(Constants.HalfDayHourCount); + } + + if (beginHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } + } + } else if (hasRightPm) { + if (endHour < Constants.HalfDayHourCount) { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + + if (beginHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } else { + Duration span = Duration.between(beginDateTime, endDateTime); + if (span.toHours() > Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } + } + } else if (beginHour <= Constants.HalfDayHourCount && endHour <= Constants.HalfDayHourCount) { + // No 'am' or 'pm' indicator + if (beginHour > endHour) { + if (beginHour == Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } else { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + ret.setComment(Constants.Comment_AmPm); + } + + if (endDateTime.isBefore(beginDateTime)) { + endDateTime = endDateTime.plusHours(24); + } + + String beginStr = DateTimeFormatUtil.shortTime(beginDateTime.getHour(), beginMinute, beginSecond); + String endStr = DateTimeFormatUtil.shortTime(endDateTime.getHour(), endMinute, endSecond); + + ret.setSuccess(true); + + ret.setTimex(String.format("(%s,%s,%s)", beginStr, endStr, DateTimeFormatUtil.luisTimeSpan(Duration.between(beginDateTime, endDateTime)))); + + ret.setFutureValue(new Pair(beginDateTime, endDateTime)); + ret.setPastValue(new Pair(beginDateTime, endDateTime)); + + List subDateTimeEntities = new ArrayList<>(); + + // In SplitDateAndTime mode, time points will be get from these SubDateTimeEntities + // Cases like "from 4 to 5pm", "4" should not be treated as SubDateTimeEntity + if (hasLeft || beginMinute != Constants.InvalidMinute || beginSecond != Constants.InvalidSecond) { + ExtractResult er = new ExtractResult( + time1StartIndex, + time1EndIndex - time1StartIndex, + text.substring(time1StartIndex, time1EndIndex), + Constants.SYS_DATETIME_TIME); + + DateTimeParseResult pr = this.config.getTimeParser().parse(er, referenceTime); + subDateTimeEntities.add(pr); + } + + // Cases like "from 4am to 5", "5" should not be treated as SubDateTimeEntity + if (hasRight || endMinute != Constants.InvalidMinute || endSecond != Constants.InvalidSecond) { + ExtractResult er = new ExtractResult( + + time2StartIndex, + time2EndIndex - time2StartIndex, + text.substring(time2StartIndex, time2EndIndex), + Constants.SYS_DATETIME_TIME + ); + + DateTimeParseResult pr = this.config.getTimeParser().parse(er, referenceTime); + subDateTimeEntities.add(pr); + } + ret.setSubDateTimeEntities(subDateTimeEntities); + ret.setSuccess(true); + } + + return ret; + } + + private DateTimeResolutionResult mergeTwoTimePoints(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + DateTimeParseResult pr1 = null; + DateTimeParseResult pr2 = null; + boolean validTimeNumber = false; + + List ers = this.config.getTimeExtractor().extract(text, referenceTime); + if (ers.size() != 2) { + if (ers.size() == 1) { + List numErs = this.config.getIntegerExtractor().extract(text); + int erStart = ers.get(0).getStart() != null ? ers.get(0).getStart() : 0; + int erLength = ers.get(0).getLength() != null ? ers.get(0).getLength() : 0; + + for (ExtractResult num : numErs) { + int numStart = num.getStart() != null ? num.getStart() : 0; + int numLength = num.getLength() != null ? num.getLength() : 0; + int midStrBegin = 0; + int midStrEnd = 0; + // ending number + if (numStart > erStart + erLength) { + midStrBegin = erStart + erLength; + midStrEnd = numStart - midStrBegin; + } else if (numStart + numLength < erStart) { + midStrBegin = numStart + numLength; + midStrEnd = erStart - midStrBegin; + } + + // check if the middle string between the time point and the valid number is a connect string. + String middleStr = text.substring(midStrBegin, midStrBegin + midStrEnd); + Optional tillMatch = Arrays.stream(RegExpUtility.getMatches(this.config.getTillRegex(), middleStr)).findFirst(); + if (tillMatch.isPresent()) { + num.setData(null); + num.setType(Constants.SYS_DATETIME_TIME); + ers.add(num); + validTimeNumber = true; + break; + } + } + + ers.sort(Comparator.comparingInt(x -> x.getStart())); + } + + if (!validTimeNumber) { + return ret; + } + } + + pr1 = this.config.getTimeParser().parse(ers.get(0), referenceTime); + pr2 = this.config.getTimeParser().parse(ers.get(1), referenceTime); + + if (pr1.getValue() == null || pr2.getValue() == null) { + return ret; + } + + String ampmStr1 = ((DateTimeResolutionResult)pr1.getValue()).getComment(); + String ampmStr2 = ((DateTimeResolutionResult)pr2.getValue()).getComment(); + + LocalDateTime beginTime = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue(); + LocalDateTime endTime = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue(); + + if (!StringUtility.isNullOrEmpty(ampmStr2) && ampmStr2.endsWith(Constants.Comment_AmPm) && + (endTime.compareTo(beginTime) < 1) && endTime.plusHours(Constants.HalfDayHourCount).isAfter(beginTime)) { + endTime = endTime.plusHours(Constants.HalfDayHourCount); + ((DateTimeResolutionResult)pr2.getValue()).setFutureValue(endTime); + pr2.setTimexStr(String.format("T%s", endTime.getHour())); + if (endTime.getMinute() > 0) { + pr2.setTimexStr(String.format("%s:%s", pr2.getTimexStr(), endTime.getMinute())); + } + } + + if (!StringUtility.isNullOrEmpty(ampmStr1) && ampmStr1.endsWith(Constants.Comment_AmPm) && endTime.isAfter(beginTime.plusHours(Constants.HalfDayHourCount))) { + beginTime = beginTime.plusHours(Constants.HalfDayHourCount); + ((DateTimeResolutionResult)pr1.getValue()).setFutureValue(beginTime); + pr1.setTimexStr(String.format("T%s", beginTime.getHour())); + if (beginTime.getMinute() > 0) { + pr1.setTimexStr(String.format("%s:%s", pr1.getTimexStr(), beginTime.getMinute())); + } + } + + if (endTime.isBefore(beginTime)) { + endTime = endTime.plusDays(1); + } + + long minutes = (Duration.between(beginTime, endTime).toMinutes() % 60); + long hours = (Duration.between(beginTime, endTime).toHours() % 24); + ret.setTimex(String.format("(%s,%s,PT", pr1.getTimexStr(), pr2.getTimexStr())); + + if (hours > 0) { + ret.setTimex(String.format("%s%sH", ret.getTimex(), hours)); + } + if (minutes > 0) { + ret.setTimex(String.format("%s%sM", ret.getTimex(), minutes)); + } + ret.setTimex(ret.getTimex() + ")"); + + ret.setFutureValue(new Pair(beginTime, endTime)); + ret.setPastValue(new Pair(beginTime, endTime)); + ret.setSuccess(true); + + if (!StringUtility.isNullOrEmpty(ampmStr1) && ampmStr1.endsWith(Constants.Comment_AmPm) && + !StringUtility.isNullOrEmpty(ampmStr2) && ampmStr2.endsWith(Constants.Comment_AmPm)) { + ret.setComment(Constants.Comment_AmPm); + } + + if (((DateTimeResolutionResult)pr1.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr1.getValue()).getTimeZoneResolution()); + } else if (((DateTimeResolutionResult)pr2.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr2.getValue()).getTimeZoneResolution()); + } + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(pr1); + subDateTimeEntities.add(pr2); + ret.setSubDateTimeEntities(subDateTimeEntities); + + return ret; + } + + private DateTimeResolutionResult parseTimeOfDay(String text, LocalDateTime referenceTime) { + int day = referenceTime.getDayOfMonth(); + int month = referenceTime.getMonthValue(); + int year = referenceTime.getYear(); + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + // extract early/late prefix from text + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getTimeOfDayRegex(), text)).findFirst(); + boolean hasEarly = false; + boolean hasLate = false; + if (match.isPresent()) { + if (!StringUtility.isNullOrEmpty(match.get().getGroup("early").value)) { + String early = match.get().getGroup("early").value; + text = text.replace(early, ""); + hasEarly = true; + ret.setComment(Constants.Comment_Early); + ret.setMod(Constants.EARLY_MOD); + } + + if (!hasEarly && !StringUtility.isNullOrEmpty(match.get().getGroup("late").value)) { + String late = match.get().getGroup("late").value; + text = text.replace(late, ""); + hasLate = true; + ret.setComment(Constants.Comment_Late); + ret.setMod(Constants.LATE_MOD); + } + } + MatchedTimeRangeResult timexResult = this.config.getMatchedTimexRange(text, "", 0, 0, 0); + if (!timexResult.getMatched()) { + return new DateTimeResolutionResult(); + } + + // modify time period if "early" or "late" is existed + if (hasEarly) { + timexResult.setEndHour(timexResult.getBeginHour() + 2); + // handling case: night end with 23:59 + if (timexResult.getEndMin() == 59) { + timexResult.setEndMin(0); + } + } else if (hasLate) { + timexResult.setBeginHour(timexResult.getBeginHour() + 2); + } + + ret.setTimex(timexResult.getTimeStr()); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getEndHour(), timexResult.getEndMin(), timexResult.getEndMin()))); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getEndHour(), timexResult.getEndMin(), timexResult.getEndMin()))); + + ret.setSuccess(true); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java new file mode 100644 index 000000000..79bd6a34d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java @@ -0,0 +1,168 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.resources.EnglishTimeZone; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneResolutionResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BaseTimeZoneParser implements IDateTimeParser { + private final Pattern directUtcRegex; + + public BaseTimeZoneParser() { + directUtcRegex = RegExpUtility.getSafeRegExp(EnglishTimeZone.DirectUtcRegex); + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_TIME; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + public String normalizeText(String text) { + text = text.replaceAll("\\s+", " "); + text = text.replaceAll("time$|timezone$", ""); + return text.trim(); + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + DateTimeParseResult result; + result = new DateTimeParseResult(er); + + String text = er.getText().toLowerCase(); + String normalizedText = normalizeText(text); + Matcher match = directUtcRegex.matcher(text); + String matched = match.find() ? match.group(2) : ""; + int offsetInMinutes = matched != null ? computeMinutes(matched) : Constants.InvalidOffsetValue; + + if (offsetInMinutes != Constants.InvalidOffsetValue) { + DateTimeResolutionResult value = getDateTimeResolutionResult(offsetInMinutes, text); + String resolutionStr = String.format("%s: %d", Constants.UtcOffsetMinsKey, offsetInMinutes); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } else if (checkAbbrToMin(normalizedText)) { + int utcMinuteShift = EnglishTimeZone.AbbrToMinMapping.getOrDefault(normalizedText, 0); + + DateTimeResolutionResult value = getDateTimeResolutionResult(utcMinuteShift, text); + String resolutionStr = String.format("%s: %d", Constants.UtcOffsetMinsKey, utcMinuteShift); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } else if (checkFullToMin(normalizedText)) { + int utcMinuteShift = EnglishTimeZone.FullToMinMapping.getOrDefault(normalizedText, 0); + + DateTimeResolutionResult value = getDateTimeResolutionResult(utcMinuteShift, text); + String resolutionStr = String.format("%s: %d", Constants.UtcOffsetMinsKey, utcMinuteShift); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } else { + // TODO: Temporary solution for city timezone and ambiguous data + DateTimeResolutionResult value = new DateTimeResolutionResult(); + value.setSuccess(true); + value.setTimeZoneResolution(new TimeZoneResolutionResult("UTC+XX:XX", Constants.InvalidOffsetValue, text)); + String resolutionStr = String.format("%s: %s", Constants.UtcOffsetMinsKey, "XX:XX"); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } + + return result; + } + + private boolean checkAbbrToMin(String text) { + if (EnglishTimeZone.AbbrToMinMapping.containsKey(text)) { + return EnglishTimeZone.AbbrToMinMapping.get(text) != Constants.InvalidOffsetValue; + } + return false; + } + + private boolean checkFullToMin(String text) { + if (EnglishTimeZone.FullToMinMapping.containsKey(text)) { + return EnglishTimeZone.FullToMinMapping.get(text) != Constants.InvalidOffsetValue; + } + return false; + } + + private DateTimeResolutionResult getDateTimeResolutionResult(int offsetInMinutes, String text) { + DateTimeResolutionResult value = new DateTimeResolutionResult(); + value.setSuccess(true); + value.setTimeZoneResolution(new TimeZoneResolutionResult(convertOffsetInMinsToOffsetString(offsetInMinutes), offsetInMinutes, text)); + return value; + } + + private String convertOffsetInMinsToOffsetString(int offsetInMinutes) { + return String.format("UTC%s%s", offsetInMinutes >= 0 ? "+" : "-", convertMinsToRegularFormat(Math.abs(offsetInMinutes))); + } + + private String convertMinsToRegularFormat(int offsetMins) { + Duration duration = Duration.ofMinutes(offsetMins); + return String.format("%02d:%02d", duration.toHours() % 24, duration.toMinutes() % 60); + } + + // Compute UTC offset in minutes from matched timezone offset in text. e.g. "-4:30" -> -270; "+8"-> 480. + public int computeMinutes(String utcOffset) { + if (utcOffset.length() == 0) { + return Constants.InvalidOffsetValue; + } + + utcOffset = utcOffset.trim(); + int sign = Constants.PositiveSign; // later than utc, default value + if (utcOffset.startsWith("+") || utcOffset.startsWith("-") || utcOffset.startsWith("±")) { + if (utcOffset.startsWith("-")) { + sign = Constants.NegativeSign; // earlier than utc 0 + } + + utcOffset = utcOffset.substring(1).trim(); + } + + int hours = 0; + final int minutes; + if (utcOffset.contains(":")) { + String[] tokens = utcOffset.split(":"); + hours = Integer.parseInt(tokens[0]); + minutes = Integer.parseInt(tokens[1]); + } else { + minutes = 0; + try { + hours = Integer.parseInt(utcOffset); + } catch (Exception e) { + hours = 0; + } + } + + if (hours > Constants.HalfDayHourCount) { + return Constants.InvalidOffsetValue; + } + + if (Arrays.stream(new int[]{0, 15, 30, 45, 60}).anyMatch(x -> x == minutes)) { + return Constants.InvalidOffsetValue; + } + + int offsetInMinutes = hours * 60 + minutes; + offsetInMinutes *= sign; + + return offsetInMinutes; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java new file mode 100644 index 000000000..62b6a9f17 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.ParseResult; + +public class DateTimeParseResult extends ParseResult { + //TimexStr is only used in extractors related with date and time + //It will output the TIMEX representation of a time string. + private String timexStr; + + public DateTimeParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr, String timexStr) { + super(start, length, text, type, data, value, resolutionStr); + this.timexStr = timexStr; + } + + public DateTimeParseResult(ExtractResult er) { + this(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), null, null, null); + } + + public DateTimeParseResult(ParseResult pr) { + this(pr.getStart(), pr.getLength(), pr.getText(), pr.getType(), pr.getData(), pr.getValue(), pr.getResolutionStr(), null); + } + + public DateTimeParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr, String timexStr, Metadata metadata) { + super(start, length, text, type, data, value, resolutionStr, metadata); + this.timexStr = timexStr; + } + + public String getTimexStr() { + return timexStr; + } + + public void setTimexStr(String timexStr) { + this.timexStr = timexStr; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java new file mode 100644 index 000000000..e88ae76da --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; + +import java.time.LocalDateTime; +import java.util.List; + +public interface IDateTimeParser extends IParser { + String getParserName(); + + DateTimeParseResult parse(ExtractResult er, LocalDateTime reference); + + List filterResults(String query, List candidateResults); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java new file mode 100644 index 000000000..c72112364 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; + +public abstract class BaseDateParserConfiguration extends BaseOptionsConfiguration implements ICommonDateTimeParserConfiguration { + protected BaseDateParserConfiguration(DateTimeOptions options) { + super(options); + } + + @Override + public ImmutableMap getDayOfMonth() { + return BaseDateTime.DayOfMonthDictionary; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..2696ac12c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java @@ -0,0 +1,80 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface ICommonDateTimeParserConfiguration extends IOptionsConfiguration { + IExtractor getCardinalExtractor(); + + IExtractor getIntegerExtractor(); + + IExtractor getOrdinalExtractor(); + + IParser getNumberParser(); + + IDateExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDateTimePeriodExtractor(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IDateTimeParser getDateTimeParser(); + + IDateTimeParser getDurationParser(); + + IDateTimeParser getDatePeriodParser(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeParser getDateTimePeriodParser(); + + IDateTimeParser getDateTimeAltParser(); + + IDateTimeParser getTimeZoneParser(); + + ImmutableMap getMonthOfYear(); + + ImmutableMap getNumbers(); + + ImmutableMap getUnitValueMap(); + + ImmutableMap getSeasonMap(); + + ImmutableMap getSpecialYearPrefixesMap(); + + ImmutableMap getUnitMap(); + + ImmutableMap getCardinalMap(); + + ImmutableMap getDayOfMonth(); + + ImmutableMap getDayOfWeek(); + + ImmutableMap getDoubleNumbers(); + + ImmutableMap getWrittenDecades(); + + ImmutableMap getSpecialDecadeCases(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java new file mode 100644 index 000000000..0f288f7a6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java @@ -0,0 +1,100 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.List; +import java.util.regex.Pattern; + +public interface IDateParserConfiguration extends IOptionsConfiguration { + String getDateTokenPrefix(); + + IExtractor getIntegerExtractor(); + + IExtractor getOrdinalExtractor(); + + IExtractor getCardinalExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateExtractor getDateExtractor(); + + IDateTimeParser getDurationParser(); + + Iterable getDateRegexes(); + + Pattern getOnRegex(); + + Pattern getSpecialDayRegex(); + + Pattern getSpecialDayWithNumRegex(); + + Pattern getNextRegex(); + + Pattern getThisRegex(); + + Pattern getLastRegex(); + + Pattern getUnitRegex(); + + Pattern getWeekDayRegex(); + + Pattern getMonthRegex(); + + Pattern getWeekDayOfMonthRegex(); + + Pattern getForTheRegex(); + + Pattern getWeekDayAndDayOfMonthRegex(); + + Pattern getRelativeMonthRegex(); + + Pattern getStrictRelativeRegex(); + + Pattern getYearSuffix(); + + Pattern getRelativeWeekDayRegex(); + + Pattern getRelativeDayRegex(); + + Pattern getNextPrefixRegex(); + + Pattern getPastPrefixRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getDayOfMonth(); + + ImmutableMap getDayOfWeek(); + + ImmutableMap getMonthOfYear(); + + ImmutableMap getCardinalMap(); + + List getSameDayTerms(); + + List getPlusOneDayTerms(); + + List getMinusOneDayTerms(); + + List getPlusTwoDayTerms(); + + List getMinusTwoDayTerms(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + Integer getSwiftMonthOrYear(String text); + + Boolean isCardinalLast(String text); + + String normalize(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java new file mode 100644 index 000000000..b32018ec7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java @@ -0,0 +1,155 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; + +import java.util.regex.Pattern; + +public interface IDatePeriodParserConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + IDateExtractor getDateExtractor(); + + IExtractor getCardinalExtractor(); + + IExtractor getOrdinalExtractor(); + + IExtractor getIntegerExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeParser getDurationParser(); + + IDateTimeParser getDateParser(); + + Pattern getMonthFrontBetweenRegex(); + + Pattern getBetweenRegex(); + + Pattern getMonthFrontSimpleCasesRegex(); + + Pattern getSimpleCasesRegex(); + + Pattern getOneWordPeriodRegex(); + + Pattern getMonthWithYear(); + + Pattern getMonthNumWithYear(); + + Pattern getYearRegex(); + + Pattern getPastRegex(); + + Pattern getFutureRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getWeekOfMonthRegex(); + + Pattern getWeekOfYearRegex(); + + Pattern getQuarterRegex(); + + Pattern getQuarterRegexYearFront(); + + Pattern getAllHalfYearRegex(); + + Pattern getSeasonRegex(); + + Pattern getWhichWeekRegex(); + + Pattern getWeekOfRegex(); + + Pattern getMonthOfRegex(); + + Pattern getInConnectorRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getNextPrefixRegex(); + + Pattern getPastPrefixRegex(); + + Pattern getThisPrefixRegex(); + + Pattern getRestOfDateRegex(); + + Pattern getLaterEarlyPeriodRegex(); + + Pattern getWeekWithWeekDayRangeRegex(); + + Pattern getYearPlusNumberRegex(); + + Pattern getDecadeWithCenturyRegex(); + + Pattern getYearPeriodRegex(); + + Pattern getComplexDatePeriodRegex(); + + Pattern getRelativeDecadeRegex(); + + Pattern getReferenceDatePeriodRegex(); + + Pattern getAgoRegex(); + + Pattern getLaterRegex(); + + Pattern getLessThanRegex(); + + Pattern getMoreThanRegex(); + + Pattern getCenturySuffixRegex(); + + Pattern getRelativeRegex(); + + Pattern getUnspecificEndOfRangeRegex(); + + Pattern getNowRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getCardinalMap(); + + ImmutableMap getDayOfMonth(); + + ImmutableMap getMonthOfYear(); + + ImmutableMap getSeasonMap(); + + ImmutableMap getSpecialYearPrefixesMap(); + + ImmutableMap getWrittenDecades(); + + ImmutableMap getNumbers(); + + ImmutableMap getSpecialDecadeCases(); + + boolean isFuture(String text); + + boolean isYearToDate(String text); + + boolean isMonthToDate(String text); + + boolean isWeekOnly(String text); + + boolean isWeekend(String text); + + boolean isMonthOnly(String text); + + boolean isYearOnly(String text); + + int getSwiftYear(String text); + + int getSwiftDayOrMonth(String text); + + boolean isLastCardinal(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..6b1139546 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; + +public interface IDateTimeAltParserConfiguration { + IDateTimeParser getDateTimeParser(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IDateTimeParser getDateTimePeriodParser(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeParser getDatePeriodParser(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java new file mode 100644 index 000000000..30303ee29 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java @@ -0,0 +1,70 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface IDateTimeParserConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + String getTokenBeforeTime(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IExtractor getCardinalExtractor(); + + IExtractor getIntegerExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeParser getDurationParser(); + + Pattern getNowRegex(); + + Pattern getAMTimeRegex(); + + Pattern getPMTimeRegex(); + + Pattern getSimpleTimeOfTodayAfterRegex(); + + Pattern getSimpleTimeOfTodayBeforeRegex(); + + Pattern getSpecificTimeOfDayRegex(); + + Pattern getSpecificEndOfRegex(); + + Pattern getUnspecificEndOfRegex(); + + Pattern getUnitRegex(); + + Pattern getDateNumberConnectorRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getNumbers(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + boolean containsAmbiguousToken(String text, String matchedText); + + ResultTimex getMatchedNowTimex(String text); + + int getSwiftDay(String text); + + int getHour(String text, int hour); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..d74d1da68 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java @@ -0,0 +1,84 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; + +import java.util.regex.Pattern; + +public interface IDateTimePeriodParserConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IExtractor getCardinalExtractor(); + + IParser getNumberParser(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IDateTimeParser getDateTimeParser(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeParser getDurationParser(); + + IDateTimeParser getTimeZoneParser(); + + Pattern getPureNumberFromToRegex(); + + Pattern getPureNumberBetweenAndRegex(); + + Pattern getSpecificTimeOfDayRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getPastRegex(); + + Pattern getFutureRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getNumberCombinedWithUnitRegex(); + + Pattern getUnitRegex(); + + Pattern getPeriodTimeOfDayWithDateRegex(); + + Pattern getRelativeTimeUnitRegex(); + + Pattern getRestOfDateTimeRegex(); + + Pattern getAmDescRegex(); + + Pattern getPmDescRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getPrefixDayRegex(); + + Pattern getBeforeRegex(); + + Pattern getAfterRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getNumbers(); + + MatchedTimeRangeResult getMatchedTimeRange(String text, String timeStr, int beginHour, int endHour, int endMin); + + int getSwiftPrefix(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java new file mode 100644 index 000000000..686ba08b2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java @@ -0,0 +1,44 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; + +import java.util.regex.Pattern; + +public interface IDurationParserConfiguration extends IOptionsConfiguration { + IExtractor getCardinalExtractor(); + + IExtractor getDurationExtractor(); + + IParser getNumberParser(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getAnUnitRegex(); + + Pattern getDuringRegex(); + + Pattern getAllDateUnitRegex(); + + Pattern getHalfDateUnitRegex(); + + Pattern getSuffixAndRegex(); + + Pattern getFollowedUnit(); + + Pattern getConjunctionRegex(); + + Pattern getInexactNumberRegex(); + + Pattern getInexactNumberUnitRegex(); + + Pattern getDurationUnitRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getUnitValueMap(); + + ImmutableMap getDoubleNumbers(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java new file mode 100644 index 000000000..b93317514 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java @@ -0,0 +1,24 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.regex.Pattern; + +public interface IHolidayParserConfiguration extends IOptionsConfiguration { + ImmutableMap getVariableHolidaysTimexDictionary(); + + ImmutableMap> getHolidayFuncDictionary(); + + ImmutableMap> getHolidayNames(); + + Iterable getHolidayRegexList(); + + int getSwiftYear(String text); + + String sanitizeHolidayToken(String holiday); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java new file mode 100644 index 000000000..8fd654b80 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +public interface IMergedParserConfiguration extends ICommonDateTimeParserConfiguration { + Pattern getBeforeRegex(); + + Pattern getAfterRegex(); + + Pattern getSinceRegex(); + + Pattern getAroundRegex(); + + Pattern getSuffixAfterRegex(); + + Pattern getYearRegex(); + + IDateTimeParser getGetParser(); + + IDateTimeParser getHolidayParser(); + + IDateTimeParser getTimeZoneParser(); + + StringMatcher getSuperfluousWordMatcher(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java new file mode 100644 index 000000000..881b20158 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java @@ -0,0 +1,58 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; + +import java.util.regex.Pattern; + +public interface ISetParserConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getDurationExtractor(); + + IDateTimeParser getDurationParser(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeParser getTimeParser(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeParser getDateParser(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeParser getDateTimeParser(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeParser getDatePeriodParser(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeExtractor getDateTimePeriodExtractor(); + + IDateTimeParser getDateTimePeriodParser(); + + ImmutableMap getUnitMap(); + + Pattern getEachPrefixRegex(); + + Pattern getPeriodicRegex(); + + Pattern getEachUnitRegex(); + + Pattern getEachDayRegex(); + + Pattern getSetWeekDayRegex(); + + Pattern getSetEachRegex(); + + MatchedTimexResult getMatchedDailyTimex(String text); + + MatchedTimexResult getMatchedUnitTimex(String text); +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java new file mode 100644 index 000000000..1f0854154 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java @@ -0,0 +1,26 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface ITimeParserConfiguration extends IOptionsConfiguration { + String getTimeTokenPrefix(); + + Pattern getAtRegex(); + + Iterable getTimeRegexes(); + + ImmutableMap getNumbers(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + IDateTimeParser getTimeZoneParser(); + + PrefixAdjustResult adjustByPrefix(String prefix, int hour, int min, boolean hasMin); + + SuffixAdjustResult adjustBySuffix(String suffix, int hour, int min, boolean hasMin, boolean hasAm, boolean hasPm); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java new file mode 100644 index 000000000..2b8bd57ad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java @@ -0,0 +1,40 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface ITimePeriodParserConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getTimeExtractor(); + + IDateTimeParser getTimeParser(); + + IExtractor getIntegerExtractor(); + + IDateTimeParser getTimeZoneParser(); + + Pattern getPureNumberFromToRegex(); + + Pattern getPureNumberBetweenAndRegex(); + + Pattern getSpecificTimeFromToRegex(); + + Pattern getSpecificTimeBetweenAndRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getGeneralEndingRegex(); + + Pattern getTillRegex(); + + ImmutableMap getNumbers(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + MatchedTimeRangeResult getMatchedTimexRange(String text, String timex, int beginHour, int endHour, int endMin); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java new file mode 100644 index 000000000..59f105384 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java @@ -0,0 +1,57 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +public class MatchedTimeRangeResult { + private boolean matched; + private String timeStr; + private int beginHour; + private int endHour; + private int endMin; + + public MatchedTimeRangeResult(boolean matched, String timeStr, int beginHour, int endHour, int endMin) { + this.matched = matched; + this.timeStr = timeStr; + this.beginHour = beginHour; + this.endHour = endHour; + this.endMin = endMin; + } + + public boolean getMatched() { + return matched; + } + + public String getTimeStr() { + return timeStr; + } + + public int getBeginHour() { + return beginHour; + } + + public int getEndHour() { + return endHour; + } + + public int getEndMin() { + return endMin; + } + + public void setMatched(boolean matched) { + this.matched = matched; + } + + public void setTimeStr(String timeStr) { + this.timeStr = timeStr; + } + + public void setBeginHour(int beginHour) { + this.beginHour = beginHour; + } + + public void setEndHour(int endHour) { + this.endHour = endHour; + } + + public void setEndMin(int endMin) { + this.endMin = endMin; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java new file mode 100644 index 000000000..7887e1c54 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +public class PrefixAdjustResult { + public final int hour; + public final int minute; + public final boolean hasMin; + + public PrefixAdjustResult(int hour, int minute, boolean hasMin) { + this.hour = hour; + this.minute = minute; + this.hasMin = hasMin; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java new file mode 100644 index 000000000..86a6a1e34 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +public class SuffixAdjustResult { + public final int hour; + public final int minute; + public final boolean hasMin; + public final boolean hasAm; + public final boolean hasPm; + + public SuffixAdjustResult(int hour, int minute, boolean hasMin, boolean hasAm, boolean hasPm) { + this.hour = hour; + this.minute = minute; + this.hasMin = hasMin; + this.hasAm = hasAm; + this.hasPm = hasPm; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java new file mode 100644 index 000000000..3add7bfae --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java @@ -0,0 +1,111 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class BaseDateTime { + + public static final String HourRegex = "(?2[0-4]|[0-1]?\\d)(h)?"; + + public static final String TwoDigitHourRegex = "(?[0-1]\\d|2[0-4])(h)?"; + + public static final String MinuteRegex = "(?[0-5]?\\d)(?!\\d)"; + + public static final String TwoDigitMinuteRegex = "(?[0-5]\\d)(?!\\d)"; + + public static final String DeltaMinuteRegex = "(?[0-5]?\\d)"; + + public static final String SecondRegex = "(?[0-5]?\\d)"; + + public static final String FourDigitYearRegex = "\\b(?((1\\d|20)\\d{2})|2100)(?!\\.0\\b)\\b"; + + public static final String HyphenDateRegex = "((?[0-9]{4})-?(?1[0-2]|0[1-9])-?(?3[01]|0[1-9]|[12][0-9]))|((?1[0-2]|0[1-9])-?(?3[01]|0[1-9]|[12][0-9])-?(?[0-9]{4}))|((?3[01]|0[1-9]|[12][0-9])-?(?1[0-2]|0[1-9])-?(?[0-9]{4}))"; + + public static final String IllegalYearRegex = "([-])({FourDigitYearRegex})([-])" + .replace("{FourDigitYearRegex}", FourDigitYearRegex); + + public static final String RangeConnectorSymbolRegex = "(--|-|—|——|~|–)"; + + public static final String BaseAmDescRegex = "(am\\b|a\\s*\\.\\s*m\\s*\\.|a[\\.]?\\s*m\\b)"; + + public static final String BasePmDescRegex = "(pm\\b|p\\s*\\.\\s*m\\s*\\.|p[\\.]?\\s*m\\b)"; + + public static final String BaseAmPmDescRegex = "(ampm)"; + + public static final String EqualRegex = "(?)="; + + public static final int MinYearNum = 1500; + + public static final int MaxYearNum = 2100; + + public static final int MaxTwoDigitYearFutureNum = 30; + + public static final int MinTwoDigitYearPastNum = 40; + + public static final ImmutableMap DayOfMonthDictionary = ImmutableMap.builder() + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("13", 13) + .put("14", 14) + .put("15", 15) + .put("16", 16) + .put("17", 17) + .put("18", 18) + .put("19", 19) + .put("20", 20) + .put("21", 21) + .put("22", 22) + .put("23", 23) + .put("24", 24) + .put("25", 25) + .put("26", 26) + .put("27", 27) + .put("28", 28) + .put("29", 29) + .put("30", 30) + .put("31", 31) + .build(); + + public static final ImmutableMap VariableHolidaysTimexDictionary = ImmutableMap.builder() + .put("fathers", "-06-WXX-7-3") + .put("mothers", "-05-WXX-7-2") + .put("thanksgiving", "-11-WXX-4-4") + .put("martinlutherking", "-01-WXX-1-3") + .put("washingtonsbirthday", "-02-WXX-1-3") + .put("canberra", "-03-WXX-1-1") + .put("labour", "-09-WXX-1-1") + .put("columbus", "-10-WXX-1-2") + .put("memorial", "-05-WXX-1-4") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java new file mode 100644 index 000000000..23ebb3069 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java @@ -0,0 +1,1016 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class ChineseDateTime { + + public static final String LangMarker = "Chi"; + + public static final String MonthRegex = "(?正月|一月|二月|三月|四月|五月|六月|七月|八月|九月|十月|十一月|十二月|01月|02月|03月|04月|05月|06月|07月|08月|09月|10月|11月|12月|1月|2月|3月|4月|5月|6月|7月|8月|9月|大年(?!龄|纪|级))"; + + public static final String DayRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|1|2|3|4|5|6|7|8|9)"; + + public static final String OneToNineIntegerRegex = "[一二三四五六七八九壹贰叁肆伍陆柒捌玖]"; + + public static final String DateDayRegexInChinese = "(?(([12][0-9]|3[01]|[1-9]|[三叁][十拾][一壹]?|[二贰貳]?[十拾]({OneToNineIntegerRegex})?|{OneToNineIntegerRegex})[日号]|初一|三十))" + .replace("{OneToNineIntegerRegex}", OneToNineIntegerRegex); + + public static final String DayRegexNumInChinese = "(?[12][0-9]|3[01]|[1-9]|[三叁][十拾][一壹]?|[二贰貳]?[十拾]({OneToNineIntegerRegex})?|{OneToNineIntegerRegex}|廿|卅)" + .replace("{OneToNineIntegerRegex}", OneToNineIntegerRegex); + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)"; + + public static final String TwoNumYear = "50"; + + public static final String YearNumRegex = "(?((1[5-9]|20)\\d{2})|2100)"; + + public static final String SimpleYearRegex = "(?(\\d{2,4}))"; + + public static final String ZeroToNineIntegerRegexChs = "[一二三四五六七八九零壹贰叁肆伍陆柒捌玖〇两千俩倆仨]"; + + public static final String DynastyStartYear = "元"; + + public static final String RegionTitleRegex = "(贞观|开元|神龙|洪武|建文|永乐|景泰|天顺|成化|嘉靖|万历|崇祯|顺治|康熙|雍正|乾隆|嘉庆|道光|咸丰|同治|光绪|宣统|民国)"; + + public static final String DynastyYearRegex = "(?{RegionTitleRegex})(?({DynastyStartYear}|\\d{1,3}|[十拾]?({ZeroToNineIntegerRegexChs}[十百拾佰]?){0,3}))" + .replace("{RegionTitleRegex}", RegionTitleRegex) + .replace("{DynastyStartYear}", DynastyStartYear) + .replace("{ZeroToNineIntegerRegexChs}", ZeroToNineIntegerRegexChs); + + public static final String DateYearInChineseRegex = "(?({ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}|{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}|{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}|{DynastyYearRegex}))" + .replace("{ZeroToNineIntegerRegexChs}", ZeroToNineIntegerRegexChs) + .replace("{DynastyYearRegex}", DynastyYearRegex); + + public static final String WeekDayRegex = "(?周日|周天|周一|周二|周三|周四|周五|周六|星期一|星期二|星期三|星期四|星期五|星期六|星期日|星期天|礼拜一|礼拜二|礼拜三|礼拜四|礼拜五|礼拜六|礼拜日|礼拜天|禮拜一|禮拜二|禮拜三|禮拜四|禮拜五|禮拜六|禮拜日|禮拜天|週日|週天|週一|週二|週三|週四|週五|週六)"; + + public static final String LunarRegex = "(农历|初一|正月|大年(?!龄|纪|级))"; + + public static final String DateThisRegex = "(这个|这一个|这|这一|本){WeekDayRegex}" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateLastRegex = "(上一个|上个|上一|上|最后一个|最后)(的)?{WeekDayRegex}" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateNextRegex = "(下一个|下个|下一|下)(的)?{WeekDayRegex}" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDayRegex = "(最近|前天|后天|昨天|明天|今天|今日|明日|昨日|大后天|大前天|後天|大後天)"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String WeekDayOfMonthRegex = "((({MonthRegex}|{MonthNumRegex})的\\s*)(?第一个|第二个|第三个|第四个|第五个|最后一个)\\s*{WeekDayRegex})" + .replace("{MonthRegex}", MonthRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String ThisPrefixRegex = "这个|这一个|这|这一|本|今"; + + public static final String LastPrefixRegex = "上个|上一个|上|上一|去"; + + public static final String NextPrefixRegex = "下个|下一个|下|下一|明"; + + public static final String RelativeRegex = "(?({ThisPrefixRegex}|{LastPrefixRegex}|{NextPrefixRegex}))" + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{LastPrefixRegex}", LastPrefixRegex) + .replace("{NextPrefixRegex}", NextPrefixRegex); + + public static final String SpecialDate = "(?({ThisPrefixRegex}|{LastPrefixRegex}|{NextPrefixRegex})年)?(?({ThisPrefixRegex}|{LastPrefixRegex}|{NextPrefixRegex})月)?{DateDayRegexInChinese}" + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{LastPrefixRegex}", LastPrefixRegex) + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{DateDayRegexInChinese}", DateDayRegexInChinese); + + public static final String DateUnitRegex = "(?年|个月|周|日|天)"; + + public static final String BeforeRegex = "以前|之前|前"; + + public static final String AfterRegex = "以后|以後|之后|之後|后|後"; + + public static final String DateRegexList1 = "({LunarRegex}(\\s*))?((({SimpleYearRegex}|{DateYearInChineseRegex})年)(\\s*))?{MonthRegex}(\\s*){DateDayRegexInChinese}((\\s*|,|,){WeekDayRegex})?" + .replace("{LunarRegex}", LunarRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DateDayRegexInChinese}", DateDayRegexInChinese) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateRegexList2 = "((({SimpleYearRegex}|{DateYearInChineseRegex})年)(\\s*))?({LunarRegex}(\\s*))?{MonthRegex}(\\s*){DateDayRegexInChinese}((\\s*|,|,){WeekDayRegex})?" + .replace("{MonthRegex}", MonthRegex) + .replace("{DateDayRegexInChinese}", DateDayRegexInChinese) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{LunarRegex}", LunarRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex); + + public static final String DateRegexList3 = "((({SimpleYearRegex}|{DateYearInChineseRegex})年)(\\s*))?({LunarRegex}(\\s*))?{MonthRegex}(\\s*)({DayRegexNumInChinese}|{DayRegex})((\\s*|,|,){WeekDayRegex})?" + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegexNumInChinese}", DayRegexNumInChinese) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{LunarRegex}", LunarRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateRegexList4 = "{MonthNumRegex}\\s*/\\s*{DayRegex}" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateRegexList5 = "{DayRegex}\\s*/\\s*{MonthNumRegex}" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String DateRegexList6 = "{MonthNumRegex}\\s*[/\\\\\\-]\\s*{DayRegex}\\s*[/\\\\\\-]\\s*{SimpleYearRegex}" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex); + + public static final String DateRegexList7 = "{DayRegex}\\s*[/\\\\\\-\\.]\\s*{MonthNumRegex}\\s*[/\\\\\\-\\.]\\s*{SimpleYearRegex}" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex); + + public static final String DateRegexList8 = "{SimpleYearRegex}\\s*[/\\\\\\-\\. ]\\s*{MonthNumRegex}\\s*[/\\\\\\-\\. ]\\s*{DayRegex}" + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DatePeriodTillRegex = "(?到|至|--|-|—|——|~|–)"; + + public static final String DatePeriodTillSuffixRequiredRegex = "(?与|和)"; + + public static final String DatePeriodDayRegexInChinese = "(?初一|三十|一日|十一日|二十一日|三十一日|二日|三日|四日|五日|六日|七日|八日|九日|十二日|十三日|十四日|十五日|十六日|十七日|十八日|十九日|二十二日|二十三日|二十四日|二十五日|二十六日|二十七日|二十八日|二十九日|一日|十一日|十日|二十一日|二十日|三十一日|三十日|二日|三日|四日|五日|六日|七日|八日|九日|十二日|十三日|十四日|十五日|十六日|十七日|十八日|十九日|二十二日|二十三日|二十四日|二十五日|二十六日|二十七日|二十八日|二十九日|十日|二十日|三十日|10日|11日|12日|13日|14日|15日|16日|17日|18日|19日|1日|20日|21日|22日|23日|24日|25日|26日|27日|28日|29日|2日|30日|31日|3日|4日|5日|6日|7日|8日|9日|一号|十一号|二十一号|三十一号|二号|三号|四号|五号|六号|七号|八号|九号|十二号|十三号|十四号|十五号|十六号|十七号|十八号|十九号|二十二号|二十三号|二十四号|二十五号|二十六号|二十七号|二十八号|二十九号|一号|十一号|十号|二十一号|二十号|三十一号|三十号|二号|三号|四号|五号|六号|七号|八号|九号|十二号|十三号|十四号|十五号|十六号|十七号|十八号|十九号|二十二号|二十三号|二十四号|二十五号|二十六号|二十七号|二十八号|二十九号|十号|二十号|三十号|10号|11号|12号|13号|14号|15号|16号|17号|18号|19号|1号|20号|21号|22号|23号|24号|25号|26号|27号|28号|29号|2号|30号|31号|3号|4号|5号|6号|7号|8号|9号|一|十一|二十一|三十一|二|三|四|五|六|七|八|九|十二|十三|十四|十五|十六|十七|十八|十九|二十二|二十三|二十四|二十五|二十六|二十七|二十八|二十九|一|十一|十|二十一|二十|三十一|三十|二|三|四|五|六|七|八|九|十二|十三|十四|十五|十六|十七|十八|十九|二十二|二十三|二十四|二十五|二十六|二十七|二十八|二十九|十|二十|三十|廿|卅)"; + + public static final String DatePeriodThisRegex = "这个|这一个|这|这一|本"; + + public static final String DatePeriodLastRegex = "上个|上一个|上|上一"; + + public static final String DatePeriodNextRegex = "下个|下一个|下|下一"; + + public static final String RelativeMonthRegex = "(?({DatePeriodThisRegex}|{DatePeriodLastRegex}|{DatePeriodNextRegex})\\s*月)" + .replace("{DatePeriodThisRegex}", DatePeriodThisRegex) + .replace("{DatePeriodLastRegex}", DatePeriodLastRegex) + .replace("{DatePeriodNextRegex}", DatePeriodNextRegex); + + public static final String HalfYearRegex = "((?(上|前)半年)|(?(下|后)半年))"; + + public static final String YearRegex = "(({YearNumRegex})(\\s*年)?|({SimpleYearRegex})\\s*年){HalfYearRegex}?" + .replace("{YearNumRegex}", YearNumRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{HalfYearRegex}", HalfYearRegex); + + public static final String StrictYearRegex = "({YearRegex}(?=[\\u4E00-\\u9FFF]|\\s|$|\\W))" + .replace("{YearRegex}", YearRegex); + + public static final String YearRegexInNumber = "(?(\\d{4}))"; + + public static final String DatePeriodYearInChineseRegex = "{DateYearInChineseRegex}年{HalfYearRegex}?" + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex) + .replace("{HalfYearRegex}", HalfYearRegex); + + public static final String MonthSuffixRegex = "(?({RelativeMonthRegex}|{MonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String SimpleCasesRegex = "((从)\\s*)?(({YearRegex}|{DatePeriodYearInChineseRegex})\\s*)?{MonthSuffixRegex}({DatePeriodDayRegexInChinese}|{DayRegex})\\s*{DatePeriodTillRegex}\\s*({DatePeriodDayRegexInChinese}|{DayRegex})((\\s+|\\s*,\\s*){YearRegex})?" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DatePeriodDayRegexInChinese}", DatePeriodDayRegexInChinese) + .replace("{DayRegex}", DayRegex) + .replace("{DatePeriodTillRegex}", DatePeriodTillRegex); + + public static final String YearAndMonth = "({DatePeriodYearInChineseRegex}|{YearRegex})\\s*{MonthRegex}" + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{YearRegex}", YearRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String PureNumYearAndMonth = "({YearRegexInNumber}\\s*[-\\.\\/]\\s*{MonthNumRegex})|({MonthNumRegex}\\s*\\/\\s*{YearRegexInNumber})" + .replace("{YearRegexInNumber}", YearRegexInNumber) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String OneWordPeriodRegex = "(((?(明|今|去)年)\\s*)?{MonthRegex}|({DatePeriodThisRegex}|{DatePeriodLastRegex}|{DatePeriodNextRegex})(?半)?\\s*(周末|周|月|年)|周末|(今|明|去|前|后)年(\\s*{HalfYearRegex})?)" + .replace("{MonthRegex}", MonthRegex) + .replace("{DatePeriodThisRegex}", DatePeriodThisRegex) + .replace("{DatePeriodLastRegex}", DatePeriodLastRegex) + .replace("{DatePeriodNextRegex}", DatePeriodNextRegex) + .replace("{HalfYearRegex}", HalfYearRegex); + + public static final String WeekOfMonthRegex = "(?{MonthSuffixRegex}的(?第一|第二|第三|第四|第五|最后一)\\s*周\\s*)" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String UnitRegex = "(?年|(个)?月|周|日|天)"; + + public static final String FollowedUnit = "^\\s*{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String NumberCombinedWithUnit = "(?\\d+(\\.\\d*)?){UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DateRangePrepositions = "((从|在|自)\\s*)?"; + + public static final String YearToYear = "({DateRangePrepositions})({DatePeriodYearInChineseRegex}|{YearRegex})\\s*({DatePeriodTillRegex}|后|後|之后|之後)\\s*({DatePeriodYearInChineseRegex}|{YearRegex})(\\s*((之间|之内|期间|中间|间)|前|之前))?" + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodTillRegex}", DatePeriodTillRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String YearToYearSuffixRequired = "({DateRangePrepositions})({DatePeriodYearInChineseRegex}|{YearRegex})\\s*({DatePeriodTillSuffixRequiredRegex})\\s*({DatePeriodYearInChineseRegex}|{YearRegex})\\s*(之间|之内|期间|中间|间)" + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodTillSuffixRequiredRegex}", DatePeriodTillSuffixRequiredRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String MonthToMonth = "({DateRangePrepositions})({MonthRegex}){DatePeriodTillRegex}({MonthRegex})" + .replace("{MonthRegex}", MonthRegex) + .replace("{DatePeriodTillRegex}", DatePeriodTillRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String MonthToMonthSuffixRequired = "({DateRangePrepositions})({MonthRegex}){DatePeriodTillSuffixRequiredRegex}({MonthRegex})\\s*(之间|之内|期间|中间|间)" + .replace("{MonthRegex}", MonthRegex) + .replace("{DatePeriodTillSuffixRequiredRegex}", DatePeriodTillSuffixRequiredRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String PastRegex = "(?(之前|前|上|近|过去))"; + + public static final String FutureRegex = "(?(之后|之後|后|後|(?春|夏|秋|冬)(天|季)?"; + + public static final String SeasonWithYear = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?{SeasonRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{SeasonRegex}", SeasonRegex); + + public static final String QuarterRegex = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(第(?1|2|3|4|一|二|三|四)季度)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String CenturyRegex = "(?\\d|1\\d|2\\d)世纪"; + + public static final String CenturyRegexInChinese = "(?一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|十六|十七|十八|十九|二十|二十一|二十二)世纪"; + + public static final String RelativeCenturyRegex = "(?({DatePeriodLastRegex}|{DatePeriodThisRegex}|{DatePeriodNextRegex}))世纪" + .replace("{DatePeriodLastRegex}", DatePeriodLastRegex) + .replace("{DatePeriodThisRegex}", DatePeriodThisRegex) + .replace("{DatePeriodNextRegex}", DatePeriodNextRegex); + + public static final String DecadeRegexInChinese = "(?十|一十|二十|三十|四十|五十|六十|七十|八十|九十)"; + + public static final String DecadeRegex = "(?({CenturyRegex}|{CenturyRegexInChinese}|{RelativeCenturyRegex}))?(?(\\d0|{DecadeRegexInChinese}))年代" + .replace("{CenturyRegex}", CenturyRegex) + .replace("{CenturyRegexInChinese}", CenturyRegexInChinese) + .replace("{RelativeCenturyRegex}", RelativeCenturyRegex) + .replace("{DecadeRegexInChinese}", DecadeRegexInChinese); + + public static final String PrepositionRegex = "(?^的|在$)"; + + public static final String NowRegex = "(?现在|马上|立刻|刚刚才|刚刚|刚才|这会儿|当下|此刻)"; + + public static final String NightRegex = "(?早|晚)"; + + public static final String TimeOfTodayRegex = "(今晚|今早|今晨|明晚|明早|明晨|昨晚)(的|在)?"; + + public static final String DateTimePeriodTillRegex = "(?到|直到|--|-|—|——)"; + + public static final String DateTimePeriodPrepositionRegex = "(?^\\s*的|在\\s*$)"; + + public static final String HourRegex = "\\b{BaseDateTime.HourRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String HourNumRegex = "(?[零〇一二两三四五六七八九]|二十[一二三四]?|十[一二三四五六七八九]?)"; + + public static final String ZhijianRegex = "^\\s*(之间|之内|期间|中间|间)"; + + public static final String DateTimePeriodThisRegex = "这个|这一个|这|这一"; + + public static final String DateTimePeriodLastRegex = "上个|上一个|上|上一"; + + public static final String DateTimePeriodNextRegex = "下个|下一个|下|下一"; + + public static final String AmPmDescRegex = "(?(am|a\\.m\\.|a m|a\\. m\\.|a\\.m|a\\. m|a m|pm|p\\.m\\.|p m|p\\. m\\.|p\\.m|p\\. m|p m))"; + + public static final String TimeOfDayRegex = "(?凌晨|清晨|早上|早间|早|上午|中午|下午|午后|晚上|夜里|夜晚|半夜|夜间|深夜|傍晚|晚)"; + + public static final String SpecificTimeOfDayRegex = "((({DateTimePeriodThisRegex}|{DateTimePeriodNextRegex}|{DateTimePeriodLastRegex})\\s+{TimeOfDayRegex})|(今晚|今早|今晨|明晚|明早|明晨|昨晚))" + .replace("{DateTimePeriodThisRegex}", DateTimePeriodThisRegex) + .replace("{DateTimePeriodNextRegex}", DateTimePeriodNextRegex) + .replace("{DateTimePeriodLastRegex}", DateTimePeriodLastRegex) + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String DateTimePeriodUnitRegex = "(个)?(?(小时|钟头|分钟|秒钟|时|分|秒))"; + + public static final String DateTimePeriodFollowedUnit = "^\\s*{DateTimePeriodUnitRegex}" + .replace("{DateTimePeriodUnitRegex}", DateTimePeriodUnitRegex); + + public static final String DateTimePeriodNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?){DateTimePeriodUnitRegex}" + .replace("{DateTimePeriodUnitRegex}", DateTimePeriodUnitRegex); + + public static final String DurationYearRegex = "((\\d{3,4})|0\\d|两千)\\s*年"; + + public static final String DurationHalfSuffixRegex = "半"; + + public static final ImmutableMap DurationSuffixList = ImmutableMap.builder() + .put("M", "分钟") + .put("S", "秒钟|秒") + .put("H", "个小时|小时|个钟头|钟头|时") + .put("D", "天") + .put("W", "星期|个星期|周") + .put("Mon", "个月") + .put("Y", "年") + .build(); + + public static final List DurationAmbiguousUnits = Arrays.asList("分钟", "秒钟", "秒", "个小时", "小时", "天", "星期", "个星期", "周", "个月", "年", "时"); + + public static final String DurationUnitRegex = "(?{DateUnitRegex}|分钟?|秒钟?|个?小时|时|个?钟头|天|个?星期|周|个?月|年)" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String DurationConnectorRegex = "^\\s*(?[多又余零]?)\\s*$"; + + public static final String LunarHolidayRegex = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(?除夕|春节|中秋节|中秋|元宵节|端午节|端午|重阳节)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String HolidayRegexList1 = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(?新年|五一|劳动节|元旦节|元旦|愚人节|平安夜|圣诞节|植树节|国庆节|情人节|教师节|儿童节|妇女节|青年节|建军节|女生节|光棍节|双十一|清明节|清明)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String HolidayRegexList2 = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(?母亲节|父亲节|感恩节|万圣节)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String SetUnitRegex = "(?年|月|周|星期|日|天|小时|时|分钟|分|秒钟|秒)"; + + public static final String SetEachUnitRegex = "(?(每个|每一|每)\\s*{SetUnitRegex})" + .replace("{SetUnitRegex}", SetUnitRegex); + + public static final String SetEachPrefixRegex = "(?(每)\\s*$)"; + + public static final String SetLastRegex = "(?last|this|next)"; + + public static final String SetEachDayRegex = "(每|每一)(天|日)\\s*$"; + + public static final String TimeHourNumRegex = "(00|01|02|03|04|05|06|07|08|09|0|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|1|2|3|4|5|6|7|8|9)"; + + public static final String TimeMinuteNumRegex = "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)"; + + public static final String TimeSecondNumRegex = "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)"; + + public static final String TimeHourChsRegex = "([零〇一二两三四五六七八九]|二十[一二三四]?|十[一二三四五六七八九]?)"; + + public static final String TimeMinuteChsRegex = "([二三四五]?十[一二三四五六七八九]?|六十|[零〇一二三四五六七八九])"; + + public static final String TimeSecondChsRegex = "{TimeMinuteChsRegex}" + .replace("{TimeMinuteChsRegex}", TimeMinuteChsRegex); + + public static final String TimeClockDescRegex = "(点\\s*整|点\\s*钟|点|时)"; + + public static final String TimeMinuteDescRegex = "(分钟|分|)"; + + public static final String TimeSecondDescRegex = "(秒钟|秒)"; + + public static final String TimeBanHourPrefixRegex = "(第)"; + + public static final String TimeHourRegex = "(?{TimeHourChsRegex}|{TimeHourNumRegex}){TimeClockDescRegex}" + .replace("{TimeBanHourPrefixRegex}", TimeBanHourPrefixRegex) + .replace("{TimeHourChsRegex}", TimeHourChsRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{TimeClockDescRegex}", TimeClockDescRegex); + + public static final String TimeMinuteRegex = "(?{TimeMinuteChsRegex}|{TimeMinuteNumRegex}){TimeMinuteDescRegex}" + .replace("{TimeMinuteChsRegex}", TimeMinuteChsRegex) + .replace("{TimeMinuteNumRegex}", TimeMinuteNumRegex) + .replace("{TimeMinuteDescRegex}", TimeMinuteDescRegex); + + public static final String TimeSecondRegex = "(?{TimeSecondChsRegex}|{TimeSecondNumRegex}){TimeSecondDescRegex}" + .replace("{TimeSecondChsRegex}", TimeSecondChsRegex) + .replace("{TimeSecondNumRegex}", TimeSecondNumRegex) + .replace("{TimeSecondDescRegex}", TimeSecondDescRegex); + + public static final String TimeHalfRegex = "(?过半|半)"; + + public static final String TimeQuarterRegex = "(?[一两二三四1-4])\\s*(刻钟|刻)"; + + public static final String TimeChineseTimeRegex = "{TimeHourRegex}({TimeQuarterRegex}|{TimeHalfRegex}|((过|又)?{TimeMinuteRegex})({TimeSecondRegex})?)?" + .replace("{TimeHourRegex}", TimeHourRegex) + .replace("{TimeQuarterRegex}", TimeQuarterRegex) + .replace("{TimeHalfRegex}", TimeHalfRegex) + .replace("{TimeMinuteRegex}", TimeMinuteRegex) + .replace("{TimeSecondRegex}", TimeSecondRegex); + + public static final String TimeDigitTimeRegex = "(?{TimeHourNumRegex}):(?{TimeMinuteNumRegex})(:(?{TimeSecondNumRegex}))?" + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{TimeMinuteNumRegex}", TimeMinuteNumRegex) + .replace("{TimeSecondNumRegex}", TimeSecondNumRegex); + + public static final String TimeDayDescRegex = "(?凌晨|清晨|早上|早间|早|上午|中午|下午|午后|晚上|夜里|夜晚|半夜|午夜|夜间|深夜|傍晚|晚)"; + + public static final String TimeApproximateDescPreffixRegex = "(大[约概]|差不多|可能|也许|约|不超过|不多[于过]|最[多长少]|少于|[超短长多]过|几乎要|将近|差点|快要|接近|至少|起码|超出|不到)"; + + public static final String TimeApproximateDescSuffixRegex = "(左右)"; + + public static final String TimeRegexes1 = "{TimeApproximateDescPreffixRegex}?{TimeDayDescRegex}?{TimeChineseTimeRegex}{TimeApproximateDescSuffixRegex}?" + .replace("{TimeApproximateDescPreffixRegex}", TimeApproximateDescPreffixRegex) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex) + .replace("{TimeApproximateDescSuffixRegex}", TimeApproximateDescSuffixRegex); + + public static final String TimeRegexes2 = "{TimeApproximateDescPreffixRegex}?{TimeDayDescRegex}?{TimeDigitTimeRegex}{TimeApproximateDescSuffixRegex}?(\\s*{AmPmDescRegex}?)" + .replace("{TimeApproximateDescPreffixRegex}", TimeApproximateDescPreffixRegex) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeDigitTimeRegex}", TimeDigitTimeRegex) + .replace("{TimeApproximateDescSuffixRegex}", TimeApproximateDescSuffixRegex) + .replace("{AmPmDescRegex}", AmPmDescRegex); + + public static final String TimeRegexes3 = "差{TimeMinuteRegex}{TimeChineseTimeRegex}" + .replace("{TimeMinuteRegex}", TimeMinuteRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex); + + public static final String TimePeriodTimePeriodConnectWords = "(起|至|到|–|-|—|~|~)"; + + public static final String TimePeriodLeftChsTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeChineseTimeRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex); + + public static final String TimePeriodRightChsTimeRegex = "{TimePeriodTimePeriodConnectWords}(?{TimeDayDescRegex}?{TimeChineseTimeRegex})(之间)?" + .replace("{TimePeriodTimePeriodConnectWords}", TimePeriodTimePeriodConnectWords) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex); + + public static final String TimePeriodLeftDigitTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeDigitTimeRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeDigitTimeRegex}", TimeDigitTimeRegex); + + public static final String TimePeriodRightDigitTimeRegex = "{TimePeriodTimePeriodConnectWords}(?{TimeDayDescRegex}?{TimeDigitTimeRegex})(之间)?" + .replace("{TimePeriodTimePeriodConnectWords}", TimePeriodTimePeriodConnectWords) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeDigitTimeRegex}", TimeDigitTimeRegex); + + public static final String TimePeriodShortLeftChsTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeHourChsRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeHourChsRegex}", TimeHourChsRegex); + + public static final String TimePeriodShortLeftDigitTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeHourNumRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex); + + public static final String TimePeriodRegexes1 = "({TimePeriodLeftDigitTimeRegex}{TimePeriodRightDigitTimeRegex}|{TimePeriodLeftChsTimeRegex}{TimePeriodRightChsTimeRegex})" + .replace("{TimePeriodLeftDigitTimeRegex}", TimePeriodLeftDigitTimeRegex) + .replace("{TimePeriodRightDigitTimeRegex}", TimePeriodRightDigitTimeRegex) + .replace("{TimePeriodLeftChsTimeRegex}", TimePeriodLeftChsTimeRegex) + .replace("{TimePeriodRightChsTimeRegex}", TimePeriodRightChsTimeRegex); + + public static final String TimePeriodRegexes2 = "({TimePeriodShortLeftDigitTimeRegex}{TimePeriodRightDigitTimeRegex}|{TimePeriodShortLeftChsTimeRegex}{TimePeriodRightChsTimeRegex})" + .replace("{TimePeriodShortLeftDigitTimeRegex}", TimePeriodShortLeftDigitTimeRegex) + .replace("{TimePeriodRightDigitTimeRegex}", TimePeriodRightDigitTimeRegex) + .replace("{TimePeriodShortLeftChsTimeRegex}", TimePeriodShortLeftChsTimeRegex) + .replace("{TimePeriodRightChsTimeRegex}", TimePeriodRightChsTimeRegex); + + public static final String FromToRegex = "(从|自).+([至到]).+"; + + public static final String AmbiguousRangeModifierPrefix = "(从|自)"; + + public static final String ParserConfigurationBefore = "((?和|或|及)?(之前|以前)|前)"; + + public static final String ParserConfigurationAfter = "((?和|或|及)?(之后|之後|以后|以後)|后|後)"; + + public static final String ParserConfigurationUntil = "(直到|直至|截至|截止(到)?)"; + + public static final String ParserConfigurationSincePrefix = "(自从|自|自打|打|从)"; + + public static final String ParserConfigurationSinceSuffix = "(以来|开始|起)"; + + public static final String ParserConfigurationLastWeekDayToken = "最后一个"; + + public static final String ParserConfigurationNextMonthToken = "下一个"; + + public static final String ParserConfigurationLastMonthToken = "上一个"; + + public static final String ParserConfigurationDatePrefix = " "; + + public static final ImmutableMap ParserConfigurationUnitMap = ImmutableMap.builder() + .put("年", "Y") + .put("月", "MON") + .put("个月", "MON") + .put("日", "D") + .put("周", "W") + .put("天", "D") + .put("小时", "H") + .put("个小时", "H") + .put("时", "H") + .put("分钟", "M") + .put("分", "M") + .put("秒钟", "S") + .put("秒", "S") + .put("星期", "W") + .put("个星期", "W") + .build(); + + public static final ImmutableMap ParserConfigurationUnitValueMap = ImmutableMap.builder() + .put("years", 31536000L) + .put("year", 31536000L) + .put("months", 2592000L) + .put("month", 2592000L) + .put("weeks", 604800L) + .put("week", 604800L) + .put("days", 86400L) + .put("day", 86400L) + .put("hours", 3600L) + .put("hour", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutes", 60L) + .put("minute", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("seconds", 1L) + .put("second", 1L) + .put("secs", 1L) + .put("sec", 1L) + .build(); + + public static final List MonthTerms = Arrays.asList("月"); + + public static final List WeekendTerms = Arrays.asList("周末"); + + public static final List WeekTerms = Arrays.asList("周", "星期"); + + public static final List YearTerms = Arrays.asList("年"); + + public static final List ThisYearTerms = Arrays.asList("今年"); + + public static final List LastYearTerms = Arrays.asList("去年"); + + public static final List NextYearTerms = Arrays.asList("明年"); + + public static final List YearAfterNextTerms = Arrays.asList("后年"); + + public static final List YearBeforeLastTerms = Arrays.asList("前年"); + + public static final ImmutableMap ParserConfigurationSeasonMap = ImmutableMap.builder() + .put("春", "SP") + .put("夏", "SU") + .put("秋", "FA") + .put("冬", "WI") + .build(); + + public static final ImmutableMap ParserConfigurationSeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap ParserConfigurationCardinalMap = ImmutableMap.builder() + .put("一", 1) + .put("二", 2) + .put("三", 3) + .put("四", 4) + .put("五", 5) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("第一个", 1) + .put("第二个", 2) + .put("第三个", 3) + .put("第四个", 4) + .put("第五个", 5) + .put("第一", 1) + .put("第二", 2) + .put("第三", 3) + .put("第四", 4) + .put("第五", 5) + .build(); + + public static final ImmutableMap ParserConfigurationDayOfMonth = ImmutableMap.builder() + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("13", 13) + .put("14", 14) + .put("15", 15) + .put("16", 16) + .put("17", 17) + .put("18", 18) + .put("19", 19) + .put("20", 20) + .put("21", 21) + .put("22", 22) + .put("23", 23) + .put("24", 24) + .put("25", 25) + .put("26", 26) + .put("27", 27) + .put("28", 28) + .put("29", 29) + .put("30", 30) + .put("31", 31) + .put("1日", 1) + .put("2日", 2) + .put("3日", 3) + .put("4日", 4) + .put("5日", 5) + .put("6日", 6) + .put("7日", 7) + .put("8日", 8) + .put("9日", 9) + .put("10日", 10) + .put("11日", 11) + .put("12日", 12) + .put("13日", 13) + .put("14日", 14) + .put("15日", 15) + .put("16日", 16) + .put("17日", 17) + .put("18日", 18) + .put("19日", 19) + .put("20日", 20) + .put("21日", 21) + .put("22日", 22) + .put("23日", 23) + .put("24日", 24) + .put("25日", 25) + .put("26日", 26) + .put("27日", 27) + .put("28日", 28) + .put("29日", 29) + .put("30日", 30) + .put("31日", 31) + .put("一日", 1) + .put("十一日", 11) + .put("二十日", 20) + .put("十日", 10) + .put("二十一日", 21) + .put("三十一日", 31) + .put("二日", 2) + .put("三日", 3) + .put("四日", 4) + .put("五日", 5) + .put("六日", 6) + .put("七日", 7) + .put("八日", 8) + .put("九日", 9) + .put("十二日", 12) + .put("十三日", 13) + .put("十四日", 14) + .put("十五日", 15) + .put("十六日", 16) + .put("十七日", 17) + .put("十八日", 18) + .put("十九日", 19) + .put("二十二日", 22) + .put("二十三日", 23) + .put("二十四日", 24) + .put("二十五日", 25) + .put("二十六日", 26) + .put("二十七日", 27) + .put("二十八日", 28) + .put("二十九日", 29) + .put("三十日", 30) + .put("1号", 1) + .put("2号", 2) + .put("3号", 3) + .put("4号", 4) + .put("5号", 5) + .put("6号", 6) + .put("7号", 7) + .put("8号", 8) + .put("9号", 9) + .put("10号", 10) + .put("11号", 11) + .put("12号", 12) + .put("13号", 13) + .put("14号", 14) + .put("15号", 15) + .put("16号", 16) + .put("17号", 17) + .put("18号", 18) + .put("19号", 19) + .put("20号", 20) + .put("21号", 21) + .put("22号", 22) + .put("23号", 23) + .put("24号", 24) + .put("25号", 25) + .put("26号", 26) + .put("27号", 27) + .put("28号", 28) + .put("29号", 29) + .put("30号", 30) + .put("31号", 31) + .put("一号", 1) + .put("十一号", 11) + .put("二十号", 20) + .put("十号", 10) + .put("二十一号", 21) + .put("三十一号", 31) + .put("二号", 2) + .put("三号", 3) + .put("四号", 4) + .put("五号", 5) + .put("六号", 6) + .put("七号", 7) + .put("八号", 8) + .put("九号", 9) + .put("十二号", 12) + .put("十三号", 13) + .put("十四号", 14) + .put("十五号", 15) + .put("十六号", 16) + .put("十七号", 17) + .put("十八号", 18) + .put("十九号", 19) + .put("二十二号", 22) + .put("二十三号", 23) + .put("二十四号", 24) + .put("二十五号", 25) + .put("二十六号", 26) + .put("二十七号", 27) + .put("二十八号", 28) + .put("二十九号", 29) + .put("三十号", 30) + .put("初一", 32) + .put("三十", 30) + .put("一", 1) + .put("十一", 11) + .put("二十", 20) + .put("十", 10) + .put("二十一", 21) + .put("三十一", 31) + .put("二", 2) + .put("三", 3) + .put("四", 4) + .put("五", 5) + .put("六", 6) + .put("七", 7) + .put("八", 8) + .put("九", 9) + .put("十二", 12) + .put("十三", 13) + .put("十四", 14) + .put("十五", 15) + .put("十六", 16) + .put("十七", 17) + .put("十八", 18) + .put("十九", 19) + .put("二十二", 22) + .put("二十三", 23) + .put("二十四", 24) + .put("二十五", 25) + .put("二十六", 26) + .put("二十七", 27) + .put("二十八", 28) + .put("二十九", 29) + .build(); + + public static final ImmutableMap ParserConfigurationDayOfWeek = ImmutableMap.builder() + .put("星期一", 1) + .put("星期二", 2) + .put("星期三", 3) + .put("星期四", 4) + .put("星期五", 5) + .put("星期六", 6) + .put("星期天", 0) + .put("星期日", 0) + .put("礼拜一", 1) + .put("礼拜二", 2) + .put("礼拜三", 3) + .put("礼拜四", 4) + .put("礼拜五", 5) + .put("礼拜六", 6) + .put("礼拜天", 0) + .put("礼拜日", 0) + .put("周一", 1) + .put("周二", 2) + .put("周三", 3) + .put("周四", 4) + .put("周五", 5) + .put("周六", 6) + .put("周日", 0) + .put("周天", 0) + .put("禮拜一", 1) + .put("禮拜二", 2) + .put("禮拜三", 3) + .put("禮拜四", 4) + .put("禮拜五", 5) + .put("禮拜六", 6) + .put("禮拜天", 0) + .put("禮拜日", 0) + .put("週一", 1) + .put("週二", 2) + .put("週三", 3) + .put("週四", 4) + .put("週五", 5) + .put("週六", 6) + .put("週日", 0) + .put("週天", 0) + .build(); + + public static final ImmutableMap ParserConfigurationMonthOfYear = ImmutableMap.builder() + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .put("一月", 1) + .put("二月", 2) + .put("三月", 3) + .put("四月", 4) + .put("五月", 5) + .put("六月", 6) + .put("七月", 7) + .put("八月", 8) + .put("九月", 9) + .put("十月", 10) + .put("十一月", 11) + .put("十二月", 12) + .put("1月", 1) + .put("2月", 2) + .put("3月", 3) + .put("4月", 4) + .put("5月", 5) + .put("6月", 6) + .put("7月", 7) + .put("8月", 8) + .put("9月", 9) + .put("10月", 10) + .put("11月", 11) + .put("12月", 12) + .put("01月", 1) + .put("02月", 2) + .put("03月", 3) + .put("04月", 4) + .put("05月", 5) + .put("06月", 6) + .put("07月", 7) + .put("08月", 8) + .put("09月", 9) + .put("正月", 13) + .put("大年", 13) + .build(); + + public static final String DateTimeSimpleAmRegex = "(?早|晨)"; + + public static final String DateTimeSimplePmRegex = "(?晚)"; + + public static final String DateTimePeriodMORegex = "(凌晨|清晨|早上|早间|早|上午)"; + + public static final String DateTimePeriodMIRegex = "(中午)"; + + public static final String DateTimePeriodAFRegex = "(下午|午后|傍晚)"; + + public static final String DateTimePeriodEVRegex = "(晚上|夜里|夜晚|晚)"; + + public static final String DateTimePeriodNIRegex = "(半夜|夜间|深夜)"; + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("早", "(? DurationUnitValueMap = ImmutableMap.builder() + .put("Y", 31536000L) + .put("Mon", 2592000L) + .put("W", 604800L) + .put("D", 86400L) + .put("H", 3600L) + .put("M", 60L) + .put("S", 1L) + .build(); + + public static final ImmutableMap HolidayNoFixedTimex = ImmutableMap.builder() + .put("父亲节", "-06-WXX-6-3") + .put("母亲节", "-05-WXX-7-2") + .put("感恩节", "-11-WXX-4-4") + .build(); + + public static final String MergedBeforeRegex = "(前|之前)$"; + + public static final String MergedAfterRegex = "(后|後|之后|之後)$"; + + public static final ImmutableMap TimeNumberDictionary = ImmutableMap.builder() + .put('零', 0) + .put('一', 1) + .put('二', 2) + .put('三', 3) + .put('四', 4) + .put('五', 5) + .put('六', 6) + .put('七', 7) + .put('八', 8) + .put('九', 9) + .put('〇', 0) + .put('两', 2) + .put('十', 10) + .build(); + + public static final ImmutableMap TimeLowBoundDesc = ImmutableMap.builder() + .put("中午", 11) + .put("下午", 12) + .put("午后", 12) + .put("晚上", 18) + .put("夜里", 18) + .put("夜晚", 18) + .put("夜间", 18) + .put("深夜", 18) + .put("傍晚", 18) + .put("晚", 18) + .put("pm", 12) + .build(); + + public static final String DefaultLanguageFallback = "YMD"; + + public static final List MorningTermList = Arrays.asList("早", "上午", "早间", "早上", "清晨"); + + public static final List MidDayTermList = Arrays.asList("中午", "正午"); + + public static final List AfternoonTermList = Arrays.asList("下午", "午后"); + + public static final List EveningTermList = Arrays.asList("晚", "晚上", "夜里", "傍晚", "夜晚"); + + public static final List DaytimeTermList = Arrays.asList("白天", "日间"); + + public static final List NightTermList = Arrays.asList("深夜"); + + public static final ImmutableMap DynastyYearMap = ImmutableMap.builder() + .put("贞观", 627) + .put("开元", 713) + .put("神龙", 705) + .put("洪武", 1368) + .put("建文", 1399) + .put("永乐", 1403) + .put("景泰", 1450) + .put("天顺", 1457) + .put("成化", 1465) + .put("嘉靖", 1522) + .put("万历", 1573) + .put("崇祯", 1628) + .put("顺治", 1644) + .put("康熙", 1662) + .put("雍正", 1723) + .put("乾隆", 1736) + .put("嘉庆", 1796) + .put("道光", 1821) + .put("咸丰", 1851) + .put("同治", 1862) + .put("光绪", 1875) + .put("宣统", 1909) + .put("民国", 1912) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java new file mode 100644 index 000000000..d24b8ef6a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java @@ -0,0 +1,1446 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishDateTime { + + public static final String LangMarker = "Eng"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?\\b(to|(un)?till?|thru|through)\\b(\\s+the\\b)?|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String RangeConnectorRegex = "(?\\b(and|through|to)\\b(\\s+the\\b)?|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String LastNegPrefix = "(?following|next|(up)?coming|this|{LastNegPrefix}last|past|previous|current|the)\\b" + .replace("{LastNegPrefix}", LastNegPrefix); + + public static final String StrictRelativeRegex = "\\b(?following|next|(up)?coming|this|{LastNegPrefix}last|past|previous|current)\\b" + .replace("{LastNegPrefix}", LastNegPrefix); + + public static final String UpcomingPrefixRegex = "((this\\s+)?((up)?coming))"; + + public static final String NextPrefixRegex = "\\b(following|next|{UpcomingPrefixRegex})\\b" + .replace("{UpcomingPrefixRegex}", UpcomingPrefixRegex); + + public static final String AfterNextSuffixRegex = "\\b(after\\s+(the\\s+)?next)\\b"; + + public static final String PastPrefixRegex = "((this\\s+)?past)\\b"; + + public static final String PreviousPrefixRegex = "({LastNegPrefix}last|previous|{PastPrefixRegex})\\b" + .replace("{LastNegPrefix}", LastNegPrefix) + .replace("{PastPrefixRegex}", PastPrefixRegex); + + public static final String ThisPrefixRegex = "(this|current)\\b"; + + public static final String RangePrefixRegex = "(from|between)"; + + public static final String CenturySuffixRegex = "(^century)\\b"; + + public static final String ReferencePrefixRegex = "(that|same)\\b"; + + public static final String FutureSuffixRegex = "\\b(in\\s+the\\s+)?(future|hence)\\b"; + + public static final String DayRegex = "(the\\s*)?(?(?:3[0-1]|[1-2]\\d|0?[1-9])(?:th|nd|rd|st)?)(?=\\b|t)"; + + public static final String ImplicitDayRegex = "(the\\s*)?(?(?:3[0-1]|[0-2]?\\d)(?:th|nd|rd|st))\\b"; + + public static final String MonthNumRegex = "(?1[0-2]|(0)?[1-9])\\b"; + + public static final String WrittenOneToNineRegex = "(?:one|two|three|four|five|six|seven|eight|nine)"; + + public static final String WrittenElevenToNineteenRegex = "(?:eleven|twelve|(?:thir|four|fif|six|seven|eigh|nine)teen)"; + + public static final String WrittenTensRegex = "(?:ten|twenty|thirty|fou?rty|fifty|sixty|seventy|eighty|ninety)"; + + public static final String WrittenNumRegex = "(?:{WrittenOneToNineRegex}|{WrittenElevenToNineteenRegex}|{WrittenTensRegex}(\\s+{WrittenOneToNineRegex})?)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex) + .replace("{WrittenElevenToNineteenRegex}", WrittenElevenToNineteenRegex) + .replace("{WrittenTensRegex}", WrittenTensRegex); + + public static final String WrittenCenturyFullYearRegex = "(?:(one|two)\\s+thousand(\\s+and)?(\\s+{WrittenOneToNineRegex}\\s+hundred(\\s+and)?)?)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final String WrittenCenturyOrdinalYearRegex = "(?:twenty(\\s+(one|two))?|ten|eleven|twelve|thirteen|fifteen|eigthteen|(?:four|six|seven|nine)(teen)?|one|two|three|five|eight)"; + + public static final String CenturyRegex = "\\b(?{WrittenCenturyFullYearRegex}|{WrittenCenturyOrdinalYearRegex}(\\s+hundred)?(\\s+and)?)\\b" + .replace("{WrittenCenturyFullYearRegex}", WrittenCenturyFullYearRegex) + .replace("{WrittenCenturyOrdinalYearRegex}", WrittenCenturyOrdinalYearRegex); + + public static final String LastTwoYearNumRegex = "(?:zero\\s+{WrittenOneToNineRegex}|{WrittenElevenToNineteenRegex}|{WrittenTensRegex}(\\s+{WrittenOneToNineRegex})?)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex) + .replace("{WrittenElevenToNineteenRegex}", WrittenElevenToNineteenRegex) + .replace("{WrittenTensRegex}", WrittenTensRegex); + + public static final String FullTextYearRegex = "\\b((?{CenturyRegex})\\s+(?{LastTwoYearNumRegex})\\b|\\b(?{WrittenCenturyFullYearRegex}|{WrittenCenturyOrdinalYearRegex}\\s+hundred(\\s+and)?))\\b" + .replace("{CenturyRegex}", CenturyRegex) + .replace("{WrittenCenturyFullYearRegex}", WrittenCenturyFullYearRegex) + .replace("{WrittenCenturyOrdinalYearRegex}", WrittenCenturyOrdinalYearRegex) + .replace("{LastTwoYearNumRegex}", LastTwoYearNumRegex); + + public static final String OclockRegex = "(?o\\s*((’|‘|')\\s*)?clock|sharp)"; + + public static final String SpecialDescRegex = "((?)p\\b)"; + + public static final String AmDescRegex = "(?:{BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "(:?{BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "(:?{BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(:?(:?({OclockRegex}\\s+)?(?({AmPmDescRegex}|{AmDescRegex}|{PmDescRegex}|{SpecialDescRegex})))|{OclockRegex})" + .replace("{OclockRegex}", OclockRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{AmPmDescRegex}", AmPmDescRegex) + .replace("{SpecialDescRegex}", SpecialDescRegex); + + public static final String OfPrepositionRegex = "(\\bof\\b)"; + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d)))\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String YearRegex = "(?:{BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String WeekDayRegex = "\\b(?(?:sun|mon|tues?|thurs?|fri)(day)?|thu|wedn(esday)?|weds?|sat(urday)?)s?\\b"; + + public static final String SingleWeekDayRegex = "\\b(?(?((day\\s+)?of\\s+)?{RelativeRegex}\\s+month)\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String WrittenMonthRegex = "(((the\\s+)?month of\\s+)?(?apr(il)?|aug(ust)?|dec(ember)?|feb(ruary)?|jan(uary)?|july?|june?|mar(ch)?|may|nov(ember)?|oct(ober)?|sept(ember)?|sept?))"; + + public static final String MonthSuffixRegex = "(?(?:(in|of|on)\\s+)?({RelativeMonthRegex}|{WrittenMonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{WrittenMonthRegex}", WrittenMonthRegex); + + public static final String DateUnitRegex = "(?decades?|years?|months?|weeks?|(?(business\\s+|week\\s*))?days?|fortnights?|weekends?|(?<=\\s+\\d{1,4})[ymwd])\\b"; + + public static final String DateTokenPrefix = "on "; + + public static final String TimeTokenPrefix = "at "; + + public static final String TokenBeforeDate = "on "; + + public static final String TokenBeforeTime = "at "; + + public static final String FromRegex = "\\b(from(\\s+the)?)$"; + + public static final String BetweenTokenRegex = "\\b(between(\\s+the)?)$"; + + public static final String SimpleCasesRegex = "\\b({RangePrefixRegex}\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex}\\s+{MonthSuffixRegex}|{MonthSuffixRegex}\\s+{DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b({RangePrefixRegex}\\s+)?{MonthSuffixRegex}\\s+((from)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+(between\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{YearRegex}", YearRegex); + + public static final String BetweenRegex = "\\b(between\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthWithYear = "\\b(({WrittenMonthRegex}[\\.]?(\\s*)[/\\\\\\-\\.,]?(\\s+(of|in))?(\\s*)({YearRegex}|(?following|next|last|this)\\s+year))|(({YearRegex}|(?following|next|last|this)\\s+year)(\\s*),?(\\s*){WrittenMonthRegex}))\\b" + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{YearRegex}", YearRegex); + + public static final String SpecialYearPrefixes = "(calendar|(?fiscal|school))"; + + public static final String OneWordPeriodRegex = "\\b((((the\\s+)?month of\\s+)?({StrictRelativeRegex}\\s+)?(?apr(il)?|aug(ust)?|dec(ember)?|feb(ruary)?|jan(uary)?|july?|june?|mar(ch)?|may|nov(ember)?|oct(ober)?|sept(ember)?|sept?))|(month|year) to date|(?((un)?till?|to)\\s+date)|({RelativeRegex}\\s+)?(my\\s+)?((?working\\s+week|workweek)|week(end)?|month|(({SpecialYearPrefixes}\\s+)?year))(?!((\\s+of)?\\s+\\d+(?!({BaseDateTime.BaseAmDescRegex}|{BaseDateTime.BasePmDescRegex}))|\\s+to\\s+date))(\\s+{AfterNextSuffixRegex})?)\\b" + .replace("{StrictRelativeRegex}", StrictRelativeRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{AfterNextSuffixRegex}", AfterNextSuffixRegex) + .replace("{SpecialYearPrefixes}", SpecialYearPrefixes) + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex) + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String MonthNumWithYear = "\\b(({BaseDateTime.FourDigitYearRegex}(\\s*)[/\\-\\.](\\s*){MonthNumRegex})|({MonthNumRegex}(\\s*)[/\\-](\\s*){BaseDateTime.FourDigitYearRegex}))\\b" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "\\b(?(the\\s+)?(?first|1st|second|2nd|third|3rd|fourth|4th|fifth|5th|last)\\s+week\\s+{MonthSuffixRegex}(\\s+{BaseDateTime.FourDigitYearRegex}|{RelativeRegex}\\s+year)?)\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String WeekOfYearRegex = "\\b(?(the\\s+)?(?first|1st|second|2nd|third|3rd|fourth|4th|fifth|5th|last)\\s+week(\\s+of)?\\s+({YearRegex}|{RelativeRegex}\\s+year))\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterTermRegex = "\\b(((?first|1st|second|2nd|third|3rd|fourth|4th)[ -]+quarter)|(q(?[1-4])))\\b"; + + public static final String RelativeQuarterTermRegex = "\\b(?{StrictRelativeRegex})\\s+quarter\\b" + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String QuarterRegex = "((the\\s+)?{QuarterTermRegex}(?:((\\s+of)?\\s+|\\s*[,-]\\s*)({YearRegex}|{RelativeRegex}\\s+year))?)|{RelativeQuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex) + .replace("{RelativeQuarterTermRegex}", RelativeQuarterTermRegex); + + public static final String QuarterRegexYearFront = "(?:{YearRegex}|{RelativeRegex}\\s+year)('s)?(?:\\s*-\\s*|\\s+(the\\s+)?)?{QuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex); + + public static final String HalfYearTermRegex = "(?first|1st|second|2nd)\\s+half"; + + public static final String HalfYearFrontRegex = "(?((1[5-9]|20)\\d{2})|2100)(\\s*-\\s*|\\s+(the\\s+)?)?h(?[1-2])" + .replace("{YearRegex}", YearRegex); + + public static final String HalfYearBackRegex = "(the\\s+)?(h(?[1-2])|({HalfYearTermRegex}))(\\s+of|\\s*,\\s*)?\\s+({YearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{HalfYearTermRegex}", HalfYearTermRegex); + + public static final String HalfYearRelativeRegex = "(the\\s+)?{HalfYearTermRegex}(\\s+of|\\s*,\\s*)?\\s+({RelativeRegex}\\s+year)" + .replace("{RelativeRegex}", RelativeRegex) + .replace("{HalfYearTermRegex}", HalfYearTermRegex); + + public static final String AllHalfYearRegex = "({HalfYearFrontRegex})|({HalfYearBackRegex})|({HalfYearRelativeRegex})" + .replace("{HalfYearFrontRegex}", HalfYearFrontRegex) + .replace("{HalfYearBackRegex}", HalfYearBackRegex) + .replace("{HalfYearRelativeRegex}", HalfYearRelativeRegex); + + public static final String EarlyPrefixRegex = "\\b(?early|beginning of|start of|(?earlier(\\s+in)?))\\b"; + + public static final String MidPrefixRegex = "\\b(?mid-?|middle of)\\b"; + + public static final String LaterPrefixRegex = "\\b(?late|end of|(?later(\\s+in)?))\\b"; + + public static final String PrefixPeriodRegex = "({EarlyPrefixRegex}|{MidPrefixRegex}|{LaterPrefixRegex})" + .replace("{EarlyPrefixRegex}", EarlyPrefixRegex) + .replace("{MidPrefixRegex}", MidPrefixRegex) + .replace("{LaterPrefixRegex}", LaterPrefixRegex); + + public static final String PrefixDayRegex = "\\b((?early)|(?mid(dle)?)|(?later?))(\\s+in)?(\\s+the\\s+day)?$"; + + public static final String SeasonDescRegex = "(?spring|summer|fall|autumn|winter)"; + + public static final String SeasonRegex = "\\b(?({PrefixPeriodRegex}\\s+)?({RelativeRegex}\\s+)?{SeasonDescRegex}((\\s+of|\\s*,\\s*)?\\s+({YearRegex}|{RelativeRegex}\\s+year))?)\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{SeasonDescRegex}", SeasonDescRegex) + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex); + + public static final String WhichWeekRegex = "\\b(week)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "(the\\s+)?((week)(\\s+(of|(commencing|starting|beginning)(\\s+on)?))|w/c)(\\s+the)?"; + + public static final String MonthOfRegex = "(month)(\\s*)(of)"; + + public static final String MonthRegex = "(?apr(il)?|aug(ust)?|dec(ember)?|feb(ruary)?|jan(uary)?|july?|june?|mar(ch)?|may|nov(ember)?|oct(ober)?|sept(ember)?|sept?)"; + + public static final String DateYearRegex = "(?{BaseDateTime.FourDigitYearRegex}|(?(3[0-1]|[0-2]?\\d)(?:th|nd|rd|st))s?)\\b"; + + public static final String PrefixWeekDayRegex = "(\\s*((,?\\s*on)|[-—–]))"; + + public static final String ThisRegex = "\\b(this(\\s*week{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|({WeekDayRegex}((\\s+of)?\\s+this\\s*week))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String LastDateRegex = "\\b({PreviousPrefixRegex}(\\s*week{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|({WeekDayRegex}(\\s+(of\\s+)?last\\s*week))\\b" + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String NextDateRegex = "\\b({NextPrefixRegex}(\\s*week{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|((on\\s+)?{WeekDayRegex}((\\s+of)?\\s+(the\\s+following|(the\\s+)?next)\\s*week))\\b" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String SpecialDayRegex = "\\b((the\\s+)?day before yesterday|(the\\s+)?day after (tomorrow|tmr)|the\\s+day\\s+(before|after)(?!=\\s+day)|((the\\s+)?({RelativeRegex}|my)\\s+day)|yesterday|tomorrow|tmr|today|otd)\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String SpecialDayWithNumRegex = "\\b((?{WrittenNumRegex})\\s+days?\\s+from\\s+(?yesterday|tomorrow|tmr|today))\\b" + .replace("{WrittenNumRegex}", WrittenNumRegex); + + public static final String RelativeDayRegex = "\\b(((the\\s+)?{RelativeRegex}\\s+day))\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String SetWeekDayRegex = "\\b(?on\\s+)?(?morning|afternoon|evening|night|(sun|mon|tues|wednes|thurs|fri|satur)day)s\\b"; + + public static final String WeekDayOfMonthRegex = "(?(the\\s+)?(?first|1st|second|2nd|third|3rd|fourth|4th|fifth|5th|last)\\s+(week\\s+{MonthSuffixRegex}[\\.]?\\s+(on\\s+)?{WeekDayRegex}|{WeekDayRegex}\\s+{MonthSuffixRegex}))" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "\\b({WrittenNumRegex}\\s+{WeekDayRegex}\\s+(from\\s+now|later))\\b" + .replace("{WrittenNumRegex}", WrittenNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDate = "(?=\\b(on|at)\\s+the\\s+){DayRegex}\\b" + .replace("{DayRegex}", DayRegex); + + public static final String DatePreposition = "\\b(on|in)"; + + public static final String DateExtractorYearTermRegex = "(\\s+|\\s*,\\s*|\\s+of\\s+){DateYearRegex}" + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor1 = "\\b({WeekDayRegex}\\s*[,-]?\\s*)?(({MonthRegex}[\\.]?\\s*[/\\\\.,-]?\\s*{DayRegex})|(\\({MonthRegex}\\s*[-.]\\s*{DayRegex}\\)))(\\s*\\(\\s*{WeekDayRegex}\\s*\\))?({DateExtractorYearTermRegex}\\b)?" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor3 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}[\\.]?(\\s+|\\s*,\\s*|\\s+of\\s+|\\s*-\\s*){MonthRegex}[\\.]?((\\s+in)?{DateExtractorYearTermRegex})?\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor4 = "\\b{MonthNumRegex}\\s*[/\\\\\\-]\\s*{DayRegex}[\\.]?\\s*[/\\\\\\-]\\s*{DateYearRegex}" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor5 = "\\b{DayRegex}\\s*[/\\\\\\-\\.]\\s*({MonthNumRegex}|{MonthRegex})\\s*[/\\\\\\-\\.]\\s*{DateYearRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor6 = "(?<={DatePreposition}\\s+)({StrictRelativeRegex}\\s+)?({WeekDayRegex}\\s+)?{MonthNumRegex}[\\-\\.]{DayRegex}(?![%])\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DatePreposition}", DatePreposition) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String DateExtractor7L = "\\b({WeekDayRegex}\\s+)?{MonthNumRegex}\\s*/\\s*{DayRegex}{DateExtractorYearTermRegex}(?![%])\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor7S = "\\b({WeekDayRegex}\\s+)?{MonthNumRegex}\\s*/\\s*{DayRegex}(?![%])\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateExtractor8 = "(?<={DatePreposition}\\s+)({StrictRelativeRegex}\\s+)?({WeekDayRegex}\\s+)?{DayRegex}[\\\\\\-]{MonthNumRegex}(?![%])\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DatePreposition}", DatePreposition) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String DateExtractor9L = "\\b({WeekDayRegex}\\s+)?{DayRegex}\\s*/\\s*{MonthNumRegex}{DateExtractorYearTermRegex}(?![%])\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor9S = "\\b({WeekDayRegex}\\s+)?{DayRegex}\\s*/\\s*{MonthNumRegex}(?![%])\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateExtractorA = "\\b({WeekDayRegex}\\s+)?{BaseDateTime.FourDigitYearRegex}\\s*[/\\\\\\-\\.]\\s*({MonthNumRegex}|{MonthRegex})\\s*[/\\\\\\-\\.]\\s*{DayRegex}" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String OfMonth = "^\\s*(day\\s+)?of\\s*{MonthRegex}" + .replace("{MonthRegex}", MonthRegex); + + public static final String MonthEnd = "{MonthRegex}\\s*(the)?\\s*$" + .replace("{MonthRegex}", MonthRegex); + + public static final String WeekDayEnd = "(this\\s+)?{WeekDayRegex}\\s*,?\\s*$" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayStart = "^[\\.]"; + + public static final String RangeUnitRegex = "\\b(?years?|months?|weeks?)\\b"; + + public static final String HourNumRegex = "\\b(?zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\\b"; + + public static final String MinuteNumRegex = "(?ten|eleven|twelve|thirteen|fifteen|eighteen|(four|six|seven|nine)(teen)?|twenty|thirty|forty|fifty|one|two|three|five|eight)"; + + public static final String DeltaMinuteNumRegex = "(?ten|eleven|twelve|thirteen|fifteen|eighteen|(four|six|seven|nine)(teen)?|twenty|thirty|forty|fifty|one|two|three|five|eight)"; + + public static final String PmRegex = "(?(((?:at|in|around|on|for)\\s+(the\\s+)?)?(afternoon|evening|midnight|lunchtime))|((at|in|around|on|for)\\s+(the\\s+)?night))"; + + public static final String PmRegexFull = "(?((?:at|in|around|on|for)\\s+(the\\s+)?)?(afternoon|evening|(mid)?night|lunchtime))"; + + public static final String AmRegex = "(?((?:at|in|around|on|for)\\s+(the\\s+)?)?(morning))"; + + public static final String LunchRegex = "\\blunchtime\\b"; + + public static final String NightRegex = "\\b(mid)?night\\b"; + + public static final String CommonDatePrefixRegex = "^[\\.]"; + + public static final String LessThanOneHour = "(?(a\\s+)?quarter|three quarter(s)?|half( an hour)?|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutes?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutes?|mins?)))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String WrittenTimeRegex = "(?{HourNumRegex}\\s+({MinuteNumRegex}|(?twenty|thirty|fou?rty|fifty)\\s+{MinuteNumRegex}))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimePrefix = "(?{LessThanOneHour}\\s+(past|to))" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?{AmRegex}|{PmRegex}|{OclockRegex})" + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String TimeSuffixFull = "(?{AmRegex}|{PmRegexFull}|{OclockRegex})" + .replace("{AmRegex}", AmRegex) + .replace("{PmRegexFull}", PmRegexFull) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "\\b(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex}(?![%\\d]))" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String MidnightRegex = "(?mid\\s*(-\\s*)?night)"; + + public static final String MidmorningRegex = "(?mid\\s*(-\\s*)?morning)"; + + public static final String MidafternoonRegex = "(?mid\\s*(-\\s*)?afternoon)"; + + public static final String MiddayRegex = "(?mid\\s*(-\\s*)?day|((12\\s)?noon))"; + + public static final String MidTimeRegex = "(?({MidnightRegex}|{MidmorningRegex}|{MidafternoonRegex}|{MiddayRegex}))" + .replace("{MidnightRegex}", MidnightRegex) + .replace("{MidmorningRegex}", MidmorningRegex) + .replace("{MidafternoonRegex}", MidafternoonRegex) + .replace("{MiddayRegex}", MiddayRegex); + + public static final String AtRegex = "\\b(?:(?:(?<=\\bat\\s+)(?:{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}(?!\\.\\d)(\\s*((?a)|(?p)))?|{MidTimeRegex}))|{MidTimeRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String IshRegex = "\\b({BaseDateTime.HourRegex}(-|——)?ish|noon(ish)?)\\b" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String TimeUnitRegex = "([^A-Za-z]{1,}|\\b)(?h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?)\\b"; + + public static final String RestrictedTimeUnitRegex = "(?hour|minute)\\b"; + + public static final String FivesRegex = "(?(?:fifteen|(?:twen|thir|fou?r|fif)ty(\\s*five)?|ten|five))\\b"; + + public static final String HourRegex = "\\b{BaseDateTime.HourRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String PeriodHourNumRegex = "\\b(?twenty(\\s+(one|two|three|four))?|eleven|twelve|thirteen|fifteen|eighteen|(four|six|seven|nine)(teen)?|zero|one|two|three|five|eight|ten)\\b"; + + public static final String ConnectNumRegex = "\\b{BaseDateTime.HourRegex}(?[0-5][0-9])\\s*{DescRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegexWithDotConnector = "({BaseDateTime.HourRegex}(\\s*\\.\\s*){BaseDateTime.MinuteRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex); + + public static final String TimeRegex1 = "\\b({TimePrefix}\\s+)?({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})(\\s*|[.]){DescRegex}" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?(?a)?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex}(\\s*{DescRegex})" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex4 = "\\b{TimePrefix}\\s+{BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex5 = "\\b{TimePrefix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex6 = "{BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffixFull}\\s+(at\\s+)?{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffixFull}", TimeSuffixFull) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = ".^" + .replace("{TimeSuffixFull}", TimeSuffixFull) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b{PeriodHourNumRegex}(\\s+|-){FivesRegex}((\\s*{DescRegex})|\\b)" + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{FivesRegex}", FivesRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex10 = "\\b({TimePrefix}\\s+)?{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex11 = "\\b((?:({TimeTokenPrefix})?{TimeRegexWithDotConnector}(\\s*{DescRegex}))|(?:(?:{TimeTokenPrefix}{TimeRegexWithDotConnector})(?!\\s*per\\s*cent|%)))" + .replace("{TimeTokenPrefix}", TimeTokenPrefix) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{DescRegex}", DescRegex); + + public static final String FirstTimeRegexInTimeRange = "\\b{TimeRegexWithDotConnector}(\\s*{DescRegex})?" + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{DescRegex}", DescRegex); + + public static final String PureNumFromTo = "({RangePrefixRegex}\\s+)?({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?\\s*{TillRegex}\\s*({HourRegex}|{PeriodHourNumRegex})(?\\s*({PmRegex}|{AmRegex}|{DescRegex}))?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String PureNumBetweenAnd = "(between\\s+)(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?\\s*{RangeConnectorRegex}\\s*(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{HourRegex}|{PeriodHourNumRegex})(?\\s*({PmRegex}|{AmRegex}|{DescRegex}))?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{BaseDateTime.TwoDigitHourRegex}", BaseDateTime.TwoDigitHourRegex) + .replace("{BaseDateTime.TwoDigitMinuteRegex}", BaseDateTime.TwoDigitMinuteRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String SpecificTimeFromTo = "({RangePrefixRegex}\\s+)?(?(({TimeRegex2}|{FirstTimeRegexInTimeRange})|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{TillRegex}\\s*(?(({TimeRegex2}|{TimeRegexWithDotConnector}(?\\s*{DescRegex}))|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{TimeRegex2}", TimeRegex2) + .replace("{FirstTimeRegexInTimeRange}", FirstTimeRegexInTimeRange) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{TillRegex}", TillRegex) + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String SpecificTimeBetweenAnd = "(between\\s+)(?(({TimeRegex2}|{FirstTimeRegexInTimeRange})|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{RangeConnectorRegex}\\s*(?(({TimeRegex2}|{TimeRegexWithDotConnector}(?\\s*{DescRegex}))|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{TimeRegex2}", TimeRegex2) + .replace("{FirstTimeRegexInTimeRange}", FirstTimeRegexInTimeRange) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String SuffixAfterRegex = "\\b(((at)\\s)?(or|and)\\s+(above|after|later|greater)(?!\\s+than))\\b"; + + public static final String PrepositionRegex = "(?^(at|on|of)(\\s+the)?$)"; + + public static final String LaterEarlyRegex = "((?early(\\s+|-))|(?late(r?\\s+|-)))"; + + public static final String MealTimeRegex = "\\b(at\\s+)?(?breakfast|brunch|lunch(\\s*time)?|dinner(\\s*time)?|supper)\\b"; + + public static final String UnspecificTimePeriodRegex = "({MealTimeRegex})" + .replace("{MealTimeRegex}", MealTimeRegex); + + public static final String TimeOfDayRegex = "\\b(?((((in\\s+the\\s+)?{LaterEarlyRegex}?(in(\\s+the)?\\s+)?(morning|afternoon|night|evening)))|{MealTimeRegex}|(((in\\s+(the)?\\s+)?)(daytime|business\\s+hour)))s?)\\b" + .replace("{LaterEarlyRegex}", LaterEarlyRegex) + .replace("{MealTimeRegex}", MealTimeRegex); + + public static final String SpecificTimeOfDayRegex = "\\b(({StrictRelativeRegex}\\s+{TimeOfDayRegex})\\b|\\btoni(ght|te))s?\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?){TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final List BusinessHourSplitStrings = Arrays.asList("business", "hour"); + + public static final String NowRegex = "\\b(?(right\\s+)?now|at th(e|is) minute|as soon as possible|asap|recently|previously)\\b"; + + public static final String NowParseRegex = "\\b({NowRegex}|^(date)$)\\b" + .replace("{NowRegex}", NowRegex); + + public static final String SuffixRegex = "^\\s*(in the\\s+)?(morning|afternoon|evening|night)\\b"; + + public static final String NonTimeContextTokens = "(building)"; + + public static final String DateTimeTimeOfDayRegex = "\\b(?morning|(?afternoon|night|evening))\\b"; + + public static final String DateTimeSpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{DateTimeTimeOfDayRegex})\\b|\\btoni(ght|te))\\b" + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String TimeOfTodayAfterRegex = "^\\s*(,\\s*)?(in\\s+)?{DateTimeSpecificTimeOfDayRegex}" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String TimeOfTodayBeforeRegex = "{DateTimeSpecificTimeOfDayRegex}(\\s*,)?(\\s+(at|around|in|on))?\\s*$" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayAfterRegex = "(?{DateUnitRegex}|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\\b" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String SuffixAndRegex = "(?\\s*(and)\\s+(an?\\s+)?(?half|quarter))"; + + public static final String PeriodicRegex = "\\b(?((?semi|bi|tri)(\\s*|-))?(daily|monthly|weekly|quarterly|yearly|annual(ly)?))\\b"; + + public static final String EachUnitRegex = "\\b(?(each|every|any|once an?)(?\\s+other)?\\s+({DurationUnitRegex}|(?quarters?|weekends?)|{WeekDayRegex})|(?weekends))" + .replace("{DurationUnitRegex}", DurationUnitRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String EachPrefixRegex = "\\b(?(each|every|once an?)\\s*$)"; + + public static final String SetEachRegex = "\\b(?(each|every)(?\\s+other)?\\s*)(?!the|that)\\b"; + + public static final String SetLastRegex = "(?following|next|upcoming|this|{LastNegPrefix}last|past|previous|current)" + .replace("{LastNegPrefix}", LastNegPrefix); + + public static final String EachDayRegex = "^\\s*(each|every)\\s*day\\b"; + + public static final String DurationFollowedUnit = "(^\\s*{DurationUnitRegex}\\s+{SuffixAndRegex})|(^\\s*{SuffixAndRegex}?(\\s+|-)?{DurationUnitRegex})" + .replace("{SuffixAndRegex}", SuffixAndRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String NumberCombinedWithDurationUnit = "\\b(?\\d+(\\.\\d*)?)(-)?{DurationUnitRegex}" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String AnUnitRegex = "(\\b((?(half)\\s+)?an?|another)|(?(1/2|½|half)))\\s+{DurationUnitRegex}" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String DuringRegex = "\\b(for|during)\\s+the\\s+(?year|month|week|day)\\b"; + + public static final String AllRegex = "\\b(?(all|full|whole)(\\s+|-)(?year|month|week|day))\\b"; + + public static final String HalfRegex = "((an?\\s*)|\\b)(?half\\s+(?year|month|week|day|hour))\\b"; + + public static final String ConjunctionRegex = "\\b((and(\\s+for)?)|with)\\b"; + + public static final String HolidayList1 = "(?mardi gras|(washington|mao)'s birthday|juneteenth|(jubilee|freedom)(\\s+day)|chinese new year|(new\\s+(years'|year\\s*'s|years?)\\s+eve)|(new\\s+(years'|year\\s*'s|years?)(\\s+day)?)|may\\s*day|yuan dan|christmas eve|(christmas|xmas)(\\s+day)?|black friday|yuandan|easter(\\s+(sunday|saturday|monday))?|clean monday|ash wednesday|palm sunday|maundy thursday|good friday|white\\s+(sunday|monday)|trinity sunday|pentecost|corpus christi|cyber monday)"; + + public static final String HolidayList2 = "(?(thanks\\s*giving|all saint's|white lover|s(?:ain)?t?(\\.)?\\s+(?:patrick|george)(?:')?(?:s)?|us independence|all hallow|all souls|guy fawkes|cinco de mayo|halloween|qingming|dragon boat|april fools|tomb\\s*sweeping)(\\s+day)?)"; + + public static final String HolidayList3 = "(?(?:independence|presidents(?:')?|mlk|martin luther king( jr)?|canberra|ascension|columbus|tree( planting)?|arbor|labou?r|((international|int'?l)\\s+)?workers'?|mother'?s?|father'?s?|female|women('s)?|single|teacher'?s|youth|children|girls|lovers?|earth|inauguration|groundhog|valentine'?s|baptiste|bastille|veterans(?:')?|memorial|mid[ \\-]autumn|moon|spring|lantern)\\s+day)"; + + public static final String HolidayRegex = "\\b(({StrictRelativeRegex}\\s+({HolidayList1}|{HolidayList2}|{HolidayList3}))|(({HolidayList1}|{HolidayList2}|{HolidayList3})(\\s+(of\\s+)?({YearRegex}|{RelativeRegex}\\s+year))?))\\b" + .replace("{HolidayList1}", HolidayList1) + .replace("{HolidayList2}", HolidayList2) + .replace("{HolidayList3}", HolidayList3) + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String AMTimeRegex = "(?morning)"; + + public static final String PMTimeRegex = "\\b(?afternoon|evening|night)\\b"; + + public static final String NightTimeRegex = "(night)"; + + public static final String NowTimeRegex = "(now|at th(e|is) minute)"; + + public static final String RecentlyTimeRegex = "(recently|previously)"; + + public static final String AsapTimeRegex = "(as soon as possible|asap)"; + + public static final String InclusiveModPrepositions = "(?((on|in|at)\\s+or\\s+)|(\\s+or\\s+(on|in|at)))"; + + public static final String AroundRegex = "(?:\\b(?:around|circa)\\s*?\\b)(\\s+the)?"; + + public static final String BeforeRegex = "((\\b{InclusiveModPrepositions}?(?:before|in\\s+advance\\s+of|prior\\s+to|(no\\s+later|earlier|sooner)\\s+than|ending\\s+(with|on)|by|(un)?till?|(?as\\s+late\\s+as)){InclusiveModPrepositions}?\\b\\s*?)|(?)((?<\\s*=)|<))(\\s+the)?" + .replace("{InclusiveModPrepositions}", InclusiveModPrepositions); + + public static final String AfterRegex = "((\\b{InclusiveModPrepositions}?((after|(starting|beginning)(\\s+on)?(?!\\sfrom)|(?>\\s*=)|>))(\\s+the)?" + .replace("{InclusiveModPrepositions}", InclusiveModPrepositions); + + public static final String SinceRegex = "(?:(?:\\b(?:since|after\\s+or\\s+equal\\s+to|starting\\s+(?:from|on|with)|as\\s+early\\s+as|(any\\s+time\\s+)from)\\b\\s*?)|(?=))(\\s+the)?"; + + public static final String SinceRegexExp = "({SinceRegex}|\\bfrom(\\s+the)?\\b)" + .replace("{SinceRegex}", SinceRegex); + + public static final String AgoRegex = "\\b(ago|before\\s+(?yesterday|today))\\b"; + + public static final String LaterRegex = "\\b(?:later(?!((\\s+in)?\\s*{OneWordPeriodRegex})|(\\s+{TimeOfDayRegex})|\\s+than\\b)|from now|(from|after)\\s+(?tomorrow|tmr|today))\\b" + .replace("{OneWordPeriodRegex}", OneWordPeriodRegex) + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String BeforeAfterRegex = "\\b((?before)|(?from|after))\\b"; + + public static final String InConnectorRegex = "\\b(in)\\b"; + + public static final String SinceYearSuffixRegex = "(^\\s*{SinceRegex}(\\s*(the\\s+)?year\\s*)?{YearSuffix})" + .replace("{SinceRegex}", SinceRegex) + .replace("{YearSuffix}", YearSuffix); + + public static final String WithinNextPrefixRegex = "\\b(within(\\s+the)?(\\s+(?{NextPrefixRegex}))?)\\b" + .replace("{NextPrefixRegex}", NextPrefixRegex); + + public static final String TodayNowRegex = "\\b(today|now)\\b"; + + public static final String MorningStartEndRegex = "(^(morning|{AmDescRegex}))|((morning|{AmDescRegex})$)" + .replace("{AmDescRegex}", AmDescRegex); + + public static final String AfternoonStartEndRegex = "(^(afternoon|{PmDescRegex}))|((afternoon|{PmDescRegex})$)" + .replace("{PmDescRegex}", PmDescRegex); + + public static final String EveningStartEndRegex = "(^(evening))|((evening)$)"; + + public static final String NightStartEndRegex = "(^(over|to)?ni(ght|te))|((over|to)?ni(ght|te)$)"; + + public static final String InexactNumberRegex = "\\b((a\\s+)?few|some|several|(?(a\\s+)?couple(\\s+of)?))\\b"; + + public static final String InexactNumberUnitRegex = "({InexactNumberRegex})\\s+({DurationUnitRegex})" + .replace("{InexactNumberRegex}", InexactNumberRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String RelativeTimeUnitRegex = "(?:(?:(?:{NextPrefixRegex}|{PreviousPrefixRegex}|{ThisPrefixRegex})\\s+({TimeUnitRegex}))|((the|my))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{TimeUnitRegex}", TimeUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String RelativeDurationUnitRegex = "(?:(?:(?<=({NextPrefixRegex}|{PreviousPrefixRegex}|{ThisPrefixRegex})\\s+)({DurationUnitRegex}))|((the|my))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String ReferenceDatePeriodRegex = "\\b{ReferencePrefixRegex}\\s+(?week|month|year|decade|weekend)\\b" + .replace("{ReferencePrefixRegex}", ReferencePrefixRegex); + + public static final String ConnectorRegex = "^(-|,|for|t|around|@)$"; + + public static final String FromToRegex = "(\\b(from).+(to|and|or)\\b.+)"; + + public static final String SingleAmbiguousMonthRegex = "^(the\\s+)?(may|march)$"; + + public static final String SingleAmbiguousTermsRegex = "^(the\\s+)?(day|week|month|year)$"; + + public static final String UnspecificDatePeriodRegex = "^(week|month|year)$"; + + public static final String PrepositionSuffixRegex = "\\b(on|in|at|around|from|to)$"; + + public static final String FlexibleDayRegex = "(?([A-Za-z]+\\s)?[A-Za-z\\d]+)"; + + public static final String ForTheRegex = "\\b((((?<=for\\s+)the\\s+{FlexibleDayRegex})|((?<=on\\s+)(the\\s+)?{FlexibleDayRegex}(?<=(st|nd|rd|th))))(?\\s*(,|\\.(?!\\d)|!|\\?|$)))" + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayOfMonthRegex = "\\b{WeekDayRegex}\\s+(the\\s+{FlexibleDayRegex})\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayRegex = "\\b{WeekDayRegex}\\s+(?!(the)){DayRegex}(?!([-:]|(\\s+({AmDescRegex}|{PmDescRegex}|{OclockRegex}))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String RestOfDateRegex = "\\b(rest|remaining)\\s+(of\\s+)?((the|my|this|current)\\s+)?(?week|month|year|decade)\\b"; + + public static final String RestOfDateTimeRegex = "\\b(rest|remaining)\\s+(of\\s+)?((the|my|this|current)\\s+)?(?day)\\b"; + + public static final String AmbiguousRangeModifierPrefix = "(from)"; + + public static final String NumberEndingPattern = "^(?:\\s+(?meeting|appointment|conference|((skype|teams|zoom|facetime)\\s+)?call)\\s+to\\s+(?{PeriodHourNumRegex}|{HourRegex})([\\.]?$|(\\.,|,|!|\\?)))" + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{HourRegex}", HourRegex); + + public static final String OneOnOneRegex = "\\b(1\\s*:\\s*1(?!\\d))|(one (on )?one|one\\s*-\\s*one|one\\s*:\\s*one)\\b"; + + public static final String LaterEarlyPeriodRegex = "\\b(({PrefixPeriodRegex})\\s*\\b\\s*(?{OneWordPeriodRegex}|(?{BaseDateTime.FourDigitYearRegex}))|({UnspecificEndOfRangeRegex}))\\b" + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex) + .replace("{OneWordPeriodRegex}", OneWordPeriodRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{UnspecificEndOfRangeRegex}", UnspecificEndOfRangeRegex); + + public static final String WeekWithWeekDayRangeRegex = "\\b((?({NextPrefixRegex}|{PreviousPrefixRegex}|this)\\s+week)((\\s+between\\s+{WeekDayRegex}\\s+and\\s+{WeekDayRegex})|(\\s+from\\s+{WeekDayRegex}\\s+to\\s+{WeekDayRegex})))\\b" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String GeneralEndingRegex = "^\\s*((\\.,)|\\.|,|!|\\?)?\\s*$"; + + public static final String MiddlePauseRegex = "\\s*(,)\\s*"; + + public static final String DurationConnectorRegex = "^\\s*(?\\s+|and|,)\\s*$"; + + public static final String PrefixArticleRegex = "\\bthe\\s+"; + + public static final String OrRegex = "\\s*((\\b|,\\s*)(or|and)\\b|,)\\s*"; + + public static final String SpecialYearTermsRegex = "\\b((({SpecialYearPrefixes}\\s+)?year)|(cy|(?fy|sy)))" + .replace("{SpecialYearPrefixes}", SpecialYearPrefixes); + + public static final String YearPlusNumberRegex = "\\b({SpecialYearTermsRegex}\\s*((?(\\d{2,4}))|{FullTextYearRegex}))\\b" + .replace("{FullTextYearRegex}", FullTextYearRegex) + .replace("{SpecialYearTermsRegex}", SpecialYearTermsRegex); + + public static final String NumberAsTimeRegex = "\\b({WrittenTimeRegex}|{PeriodHourNumRegex}|{BaseDateTime.HourRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String TimeBeforeAfterRegex = "\\b(((?<=\\b(before|no later than|by|after)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}|{MidTimeRegex}))|{MidTimeRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String DateNumberConnectorRegex = "^\\s*(?\\s+at)\\s*$"; + + public static final String DecadeRegex = "(?(?:nough|twen|thir|fou?r|fif|six|seven|eight|nine)ties|two\\s+thousands)"; + + public static final String DecadeWithCenturyRegex = "(the\\s+)?(((?\\d|1\\d|2\\d)?(')?(?\\d0)(')?(\\s)?s\\b)|(({CenturyRegex}(\\s+|-)(and\\s+)?)?{DecadeRegex})|({CenturyRegex}(\\s+|-)(and\\s+)?(?tens|hundreds)))" + .replace("{CenturyRegex}", CenturyRegex) + .replace("{DecadeRegex}", DecadeRegex); + + public static final String RelativeDecadeRegex = "\\b((the\\s+)?{RelativeRegex}\\s+((?[\\w,]+)\\s+)?decades?)\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String YearPeriodRegex = "((((from|during|in)\\s+)?{YearRegex}\\s*({TillRegex})\\s*{YearRegex})|(((between)\\s+){YearRegex}\\s*({RangeConnectorRegex})\\s*{YearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{TillRegex}", TillRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String StrictTillRegex = "(?\\b(to|(un)?till?|thru|through)\\b|{BaseDateTime.RangeConnectorSymbolRegex}(?!\\s*(h[1-2]|q[1-4])(?!(\\s+of|\\s*,\\s*))))" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String StrictRangeConnectorRegex = "(?\\b(and|through|to)\\b|{BaseDateTime.RangeConnectorSymbolRegex}(?!\\s*(h[1-2]|q[1-4])(?!(\\s+of|\\s*,\\s*))))" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String StartMiddleEndRegex = "\\b((?((the\\s+)?(start|beginning)\\s+of\\s+)?)(?((the\\s+)?middle\\s+of\\s+)?)(?((the\\s+)?end\\s+of\\s+)?))"; + + public static final String ComplexDatePeriodRegex = "(?:((from|during|in)\\s+)?{StartMiddleEndRegex}(?.+)\\s*({StrictTillRegex})\\s*{StartMiddleEndRegex}(?.+)|((between)\\s+){StartMiddleEndRegex}(?.+)\\s*({StrictRangeConnectorRegex})\\s*{StartMiddleEndRegex}(?.+))" + .replace("{StrictTillRegex}", StrictTillRegex) + .replace("{StrictRangeConnectorRegex}", StrictRangeConnectorRegex) + .replace("{StartMiddleEndRegex}", StartMiddleEndRegex); + + public static final String FailFastRegex = "{BaseDateTime.DeltaMinuteRegex}|\\b(?:{BaseDateTime.BaseAmDescRegex}|{BaseDateTime.BasePmDescRegex})|{BaseDateTime.BaseAmPmDescRegex}|\\b(?:zero|{WrittenOneToNineRegex}|{WrittenElevenToNineteenRegex}|{WrittenTensRegex}|{WrittenMonthRegex}|{SeasonDescRegex}|{DecadeRegex}|centur(y|ies)|weekends?|quarters?|hal(f|ves)|yesterday|to(morrow|day|night)|tmr|noonish|\\d(-|——)?ish|((the\\s+\\w*)|\\d)(th|rd|nd|st)|(mid\\s*(-\\s*)?)?(night|morning|afternoon|day)s?|evenings?||noon|lunch(time)?|dinner(time)?|(day|night)time|overnight|dawn|dusk|sunset|hours?|hrs?|h|minutes?|mins?|seconds?|secs?|eo[dmy]|mardi[ -]?gras|birthday|eve|christmas|xmas|thanksgiving|halloween|yuandan|easter|yuan dan|april fools|cinco de mayo|all (hallow|souls)|guy fawkes|(st )?patrick|hundreds?|noughties|aughts|thousands?)\\b|{WeekDayRegex}|{SetWeekDayRegex}|{NowRegex}|{PeriodicRegex}|\\b({DateUnitRegex}|{ImplicitDayRegex})" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex) + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex) + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex) + .replace("{ImplicitDayRegex}", ImplicitDayRegex) + .replace("{DateUnitRegex}", DateUnitRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{SetWeekDayRegex}", SetWeekDayRegex) + .replace("{NowRegex}", NowRegex) + .replace("{PeriodicRegex}", PeriodicRegex) + .replace("{DecadeRegex}", DecadeRegex) + .replace("{SeasonDescRegex}", SeasonDescRegex) + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{WrittenTensRegex}", WrittenTensRegex) + .replace("{WrittenElevenToNineteenRegex}", WrittenElevenToNineteenRegex) + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("decades", "10Y") + .put("decade", "10Y") + .put("years", "Y") + .put("year", "Y") + .put("months", "MON") + .put("month", "MON") + .put("quarters", "3MON") + .put("quarter", "3MON") + .put("semesters", "6MON") + .put("semestres", "6MON") + .put("semester", "6MON") + .put("semestre", "6MON") + .put("weeks", "W") + .put("week", "W") + .put("weekends", "WE") + .put("weekend", "WE") + .put("fortnights", "2W") + .put("fortnight", "2W") + .put("weekdays", "D") + .put("weekday", "D") + .put("days", "D") + .put("day", "D") + .put("nights", "D") + .put("night", "D") + .put("hours", "H") + .put("hour", "H") + .put("hrs", "H") + .put("hr", "H") + .put("h", "H") + .put("minutes", "M") + .put("minute", "M") + .put("mins", "M") + .put("min", "M") + .put("seconds", "S") + .put("second", "S") + .put("secs", "S") + .put("sec", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("decades", 315360000L) + .put("decade", 315360000L) + .put("years", 31536000L) + .put("year", 31536000L) + .put("months", 2592000L) + .put("month", 2592000L) + .put("fortnights", 1209600L) + .put("fortnight", 1209600L) + .put("weekends", 172800L) + .put("weekend", 172800L) + .put("weeks", 604800L) + .put("week", 604800L) + .put("days", 86400L) + .put("day", 86400L) + .put("nights", 86400L) + .put("night", 86400L) + .put("hours", 3600L) + .put("hour", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutes", 60L) + .put("minute", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("seconds", 1L) + .put("second", 1L) + .put("secs", 1L) + .put("sec", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("fiscal", "FY") + .put("school", "SY") + .put("fy", "FY") + .put("sy", "SY") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("spring", "SP") + .put("summer", "SU") + .put("fall", "FA") + .put("autumn", "FA") + .put("winter", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("first", 1) + .put("1st", 1) + .put("second", 2) + .put("2nd", 2) + .put("third", 3) + .put("3rd", 3) + .put("fourth", 4) + .put("4th", 4) + .put("fifth", 5) + .put("5th", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("monday", 1) + .put("tuesday", 2) + .put("wednesday", 3) + .put("thursday", 4) + .put("friday", 5) + .put("saturday", 6) + .put("sunday", 0) + .put("mon", 1) + .put("tue", 2) + .put("tues", 2) + .put("wed", 3) + .put("wedn", 3) + .put("weds", 3) + .put("thu", 4) + .put("thur", 4) + .put("thurs", 4) + .put("fri", 5) + .put("sat", 6) + .put("sun", 0) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("january", 1) + .put("february", 2) + .put("march", 3) + .put("april", 4) + .put("may", 5) + .put("june", 6) + .put("july", 7) + .put("august", 8) + .put("september", 9) + .put("october", 10) + .put("november", 11) + .put("december", 12) + .put("jan", 1) + .put("feb", 2) + .put("mar", 3) + .put("apr", 4) + .put("jun", 6) + .put("jul", 7) + .put("aug", 8) + .put("sep", 9) + .put("sept", 9) + .put("oct", 10) + .put("nov", 11) + .put("dec", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("zero", 0) + .put("one", 1) + .put("a", 1) + .put("an", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .put("six", 6) + .put("seven", 7) + .put("eight", 8) + .put("nine", 9) + .put("ten", 10) + .put("eleven", 11) + .put("twelve", 12) + .put("thirteen", 13) + .put("fourteen", 14) + .put("fifteen", 15) + .put("sixteen", 16) + .put("seventeen", 17) + .put("eighteen", 18) + .put("nineteen", 19) + .put("twenty", 20) + .put("twenty one", 21) + .put("twenty two", 22) + .put("twenty three", 23) + .put("twenty four", 24) + .put("twenty five", 25) + .put("twenty six", 26) + .put("twenty seven", 27) + .put("twenty eight", 28) + .put("twenty nine", 29) + .put("thirty", 30) + .put("thirty one", 31) + .put("thirty two", 32) + .put("thirty three", 33) + .put("thirty four", 34) + .put("thirty five", 35) + .put("thirty six", 36) + .put("thirty seven", 37) + .put("thirty eight", 38) + .put("thirty nine", 39) + .put("forty", 40) + .put("forty one", 41) + .put("forty two", 42) + .put("forty three", 43) + .put("forty four", 44) + .put("forty five", 45) + .put("forty six", 46) + .put("forty seven", 47) + .put("forty eight", 48) + .put("forty nine", 49) + .put("fifty", 50) + .put("fifty one", 51) + .put("fifty two", 52) + .put("fifty three", 53) + .put("fifty four", 54) + .put("fifty five", 55) + .put("fifty six", 56) + .put("fifty seven", 57) + .put("fifty eight", 58) + .put("fifty nine", 59) + .put("sixty", 60) + .put("sixty one", 61) + .put("sixty two", 62) + .put("sixty three", 63) + .put("sixty four", 64) + .put("sixty five", 65) + .put("sixty six", 66) + .put("sixty seven", 67) + .put("sixty eight", 68) + .put("sixty nine", 69) + .put("seventy", 70) + .put("seventy one", 71) + .put("seventy two", 72) + .put("seventy three", 73) + .put("seventy four", 74) + .put("seventy five", 75) + .put("seventy six", 76) + .put("seventy seven", 77) + .put("seventy eight", 78) + .put("seventy nine", 79) + .put("eighty", 80) + .put("eighty one", 81) + .put("eighty two", 82) + .put("eighty three", 83) + .put("eighty four", 84) + .put("eighty five", 85) + .put("eighty six", 86) + .put("eighty seven", 87) + .put("eighty eight", 88) + .put("eighty nine", 89) + .put("ninety", 90) + .put("ninety one", 91) + .put("ninety two", 92) + .put("ninety three", 93) + .put("ninety four", 94) + .put("ninety five", 95) + .put("ninety six", 96) + .put("ninety seven", 97) + .put("ninety eight", 98) + .put("ninety nine", 99) + .put("one hundred", 100) + .build(); + + public static final ImmutableMap DayOfMonth = ImmutableMap.builder() + .put("1st", 1) + .put("2nd", 2) + .put("3rd", 3) + .put("4th", 4) + .put("5th", 5) + .put("6th", 6) + .put("7th", 7) + .put("8th", 8) + .put("9th", 9) + .put("10th", 10) + .put("11th", 11) + .put("11st", 11) + .put("12th", 12) + .put("12nd", 12) + .put("13th", 13) + .put("13rd", 13) + .put("14th", 14) + .put("15th", 15) + .put("16th", 16) + .put("17th", 17) + .put("18th", 18) + .put("19th", 19) + .put("20th", 20) + .put("21st", 21) + .put("21th", 21) + .put("22nd", 22) + .put("22th", 22) + .put("23rd", 23) + .put("23th", 23) + .put("24th", 24) + .put("25th", 25) + .put("26th", 26) + .put("27th", 27) + .put("28th", 28) + .put("29th", 29) + .put("30th", 30) + .put("31st", 31) + .put("01st", 1) + .put("02nd", 2) + .put("03rd", 3) + .put("04th", 4) + .put("05th", 5) + .put("06th", 6) + .put("07th", 7) + .put("08th", 8) + .put("09th", 9) + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("half", 0.5D) + .put("quarter", 0.25D) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("easterday", new String[]{"easterday", "easter", "eastersunday"}) + .put("ashwednesday", new String[]{"ashwednesday"}) + .put("palmsunday", new String[]{"palmsunday"}) + .put("maundythursday", new String[]{"maundythursday"}) + .put("goodfriday", new String[]{"goodfriday"}) + .put("eastersaturday", new String[]{"eastersaturday"}) + .put("eastermonday", new String[]{"eastermonday"}) + .put("ascensionday", new String[]{"ascensionday"}) + .put("whitesunday", new String[]{"whitesunday", "pentecost", "pentecostday"}) + .put("whitemonday", new String[]{"whitemonday"}) + .put("trinitysunday", new String[]{"trinitysunday"}) + .put("corpuschristi", new String[]{"corpuschristi"}) + .put("earthday", new String[]{"earthday"}) + .put("fathers", new String[]{"fatherday", "fathersday"}) + .put("mothers", new String[]{"motherday", "mothersday"}) + .put("thanksgiving", new String[]{"thanksgivingday", "thanksgiving"}) + .put("blackfriday", new String[]{"blackfriday"}) + .put("cybermonday", new String[]{"cybermonday"}) + .put("martinlutherking", new String[]{"mlkday", "martinlutherkingday", "martinlutherkingjrday"}) + .put("washingtonsbirthday", new String[]{"washingtonsbirthday", "washingtonbirthday", "presidentsday"}) + .put("canberra", new String[]{"canberraday"}) + .put("labour", new String[]{"labourday", "laborday"}) + .put("columbus", new String[]{"columbusday"}) + .put("memorial", new String[]{"memorialday"}) + .put("yuandan", new String[]{"yuandan"}) + .put("maosbirthday", new String[]{"maosbirthday"}) + .put("teachersday", new String[]{"teachersday", "teacherday"}) + .put("singleday", new String[]{"singleday"}) + .put("allsaintsday", new String[]{"allsaintsday"}) + .put("youthday", new String[]{"youthday"}) + .put("childrenday", new String[]{"childrenday", "childday"}) + .put("femaleday", new String[]{"femaleday"}) + .put("treeplantingday", new String[]{"treeplantingday"}) + .put("arborday", new String[]{"arborday"}) + .put("girlsday", new String[]{"girlsday"}) + .put("whiteloverday", new String[]{"whiteloverday"}) + .put("loverday", new String[]{"loverday", "loversday"}) + .put("christmas", new String[]{"christmasday", "christmas"}) + .put("xmas", new String[]{"xmasday", "xmas"}) + .put("newyear", new String[]{"newyear"}) + .put("newyearday", new String[]{"newyearday"}) + .put("newyearsday", new String[]{"newyearsday"}) + .put("inaugurationday", new String[]{"inaugurationday"}) + .put("groundhougday", new String[]{"groundhougday"}) + .put("valentinesday", new String[]{"valentinesday"}) + .put("stpatrickday", new String[]{"stpatrickday", "stpatricksday", "stpatrick"}) + .put("aprilfools", new String[]{"aprilfools"}) + .put("stgeorgeday", new String[]{"stgeorgeday"}) + .put("mayday", new String[]{"mayday", "intlworkersday", "internationalworkersday", "workersday"}) + .put("cincodemayoday", new String[]{"cincodemayoday"}) + .put("baptisteday", new String[]{"baptisteday"}) + .put("usindependenceday", new String[]{"usindependenceday"}) + .put("independenceday", new String[]{"independenceday"}) + .put("bastilleday", new String[]{"bastilleday"}) + .put("halloweenday", new String[]{"halloweenday", "halloween"}) + .put("allhallowday", new String[]{"allhallowday"}) + .put("allsoulsday", new String[]{"allsoulsday"}) + .put("guyfawkesday", new String[]{"guyfawkesday"}) + .put("veteransday", new String[]{"veteransday"}) + .put("christmaseve", new String[]{"christmaseve"}) + .put("newyeareve", new String[]{"newyearseve", "newyeareve"}) + .put("juneteenth", new String[]{"juneteenth", "freedomday", "jubileeday"}) + .build(); + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("hundreds", 0) + .put("tens", 10) + .put("twenties", 20) + .put("thirties", 30) + .put("forties", 40) + .put("fifties", 50) + .put("sixties", 60) + .put("seventies", 70) + .put("eighties", 80) + .put("nineties", 90) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("noughties", 2000) + .put("aughts", 2000) + .put("two thousands", 2000) + .build(); + + public static final String DefaultLanguageFallback = "MDY"; + + public static final List SuperfluousWordList = Arrays.asList("preferably", "how about", "maybe", "perhaps", "say", "like"); + + public static final List DurationDateRestrictions = Arrays.asList("today", "now"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^(morning|afternoon|evening|night|day)\\b", "\\b(good\\s+(morning|afternoon|evening|night|day))|(nighty\\s+night)\\b") + .put("\\bnow\\b", "\\b(^now,)|\\b((is|are)\\s+now\\s+for|for\\s+now)\\b") + .put("\\bmay\\b", "\\b((((!|\\.|\\?|,|;|)\\s+|^)may i)|(i|you|he|she|we|they)\\s+may|(may\\s+((((also|not|(also not)|well)\\s+)?(be|ask|contain|constitute|e-?mail|take|have|result|involve|get|work|reply|differ))|(or may not))))\\b") + .put("\\b(a|one) second\\b", "\\b(? MorningTermList = Arrays.asList("morning"); + + public static final List AfternoonTermList = Arrays.asList("afternoon"); + + public static final List EveningTermList = Arrays.asList("evening"); + + public static final List MealtimeBreakfastTermList = Arrays.asList("breakfast"); + + public static final List MealtimeBrunchTermList = Arrays.asList("brunch"); + + public static final List MealtimeLunchTermList = Arrays.asList("lunch", "lunchtime"); + + public static final List MealtimeDinnerTermList = Arrays.asList("dinner", "dinnertime", "supper"); + + public static final List DaytimeTermList = Arrays.asList("daytime"); + + public static final List NightTermList = Arrays.asList("night"); + + public static final List SameDayTerms = Arrays.asList("today", "otd"); + + public static final List PlusOneDayTerms = Arrays.asList("tomorrow", "tmr", "day after"); + + public static final List MinusOneDayTerms = Arrays.asList("yesterday", "day before"); + + public static final List PlusTwoDayTerms = Arrays.asList("day after tomorrow", "day after tmr"); + + public static final List MinusTwoDayTerms = Arrays.asList("day before yesterday"); + + public static final List FutureTerms = Arrays.asList("this", "next"); + + public static final List LastCardinalTerms = Arrays.asList("last"); + + public static final List MonthTerms = Arrays.asList("month"); + + public static final List MonthToDateTerms = Arrays.asList("month to date"); + + public static final List WeekendTerms = Arrays.asList("weekend"); + + public static final List WeekTerms = Arrays.asList("week"); + + public static final List YearTerms = Arrays.asList("year"); + + public static final List GenericYearTerms = Arrays.asList("y"); + + public static final List YearToDateTerms = Arrays.asList("year to date"); + + public static final String DoubleMultiplierRegex = "^(bi)(-|\\s)?"; + + public static final String HalfMultiplierRegex = "^(semi)(-|\\s)?"; + + public static final String DayTypeRegex = "((week)?da(il)?ys?)$"; + + public static final String WeekTypeRegex = "(week(s|ly)?)$"; + + public static final String WeekendTypeRegex = "(weekends?)$"; + + public static final String MonthTypeRegex = "(month(s|ly)?)$"; + + public static final String QuarterTypeRegex = "(quarter(s|ly)?)$"; + + public static final String YearTypeRegex = "((years?|annual)(ly)?)$"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java new file mode 100644 index 000000000..89b29c012 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java @@ -0,0 +1,374 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishTimeZone { + + public static final String DirectUtcRegex = "\\b(utc|gmt)(\\s*[+\\-\\u00B1]?\\s*[\\d]{1,2}h?(\\s*:\\s*[\\d]{1,2})?)?\\b"; + + public static final List AbbreviationsList = Arrays.asList("ABST", "ACDT", "ACST", "ACT", "ADT", "AEDT", "AEST", "AET", "AFT", "AKDT", "AKST", "AMST", "AMT", "AOE", "AoE", "ARBST", "ARST", "ART", "AST", "AWDT", "AWST", "AZOST", "AZOT", "AZST", "AZT", "BIT", "BST", "BTT", "CADT", "CAST", "CBST", "CBT", "CCST", "CDT", "CDTM", "CEST", "CET", "COT", "CST", "CSTM", "CT", "CVT", "EAT", "ECT", "EDT", "EDTM", "EEST", "EET", "EGST", "ESAST", "ESAT", "EST", "ESTM", "ET", "FJST", "FJT", "GET", "GMT", "GNDT", "GNST", "GST", "GTBST", "HADT", "HAST", "HDT", "HKT", "HST", "IRDT", "IRKT", "IRST", "ISDT", "ISST", "IST", "JDT", "JST", "KRAT", "KST", "LINT", "MAGST", "MAGT", "MAT", "MDT", "MDTM", "MEST", "MOST", "MSK", "MSK+1", "MSK+2", "MSK+3", "MSK+4", "MSK+5", "MSK+6", "MSK+7", "MSK+8", "MSK+9", "MSK-1", "MST", "MSTM", "MUT", "MVST", "MYST", "NCAST", "NDT", "NMDT", "NMST", "NPT", "NST", "NZDT", "NZST", "NZT", "PDST", "PDT", "PDTM", "PETT", "PKT", "PSAST", "PSAT", "PST", "PSTM", "PT", "PYST", "PYT", "RST", "SAEST", "SAPST", "SAST", "SAWST", "SBT", "SGT", "SLT", "SMST", "SNST", "SST", "TADT", "TAST", "THA", "TIST", "TOST", "TOT", "TRT", "TST", "ULAT", "UTC", "VET", "VLAT", "WAST", "WAT", "WEST", "WET", "WPST", "YAKT", "YEKT"); + + public static final List FullNameList = Arrays.asList("Acre Time", "Afghanistan Standard Time", "Alaskan Standard Time", "Anywhere on Earth", "Arab Standard Time", "Arabian Standard Time", "Arabic Standard Time", "Argentina Standard Time", "Atlantic Standard Time", "AUS Central Standard Time", "Australian Central Time", "AUS Eastern Standard Time", "Australian Eastern Time", "Australian Eastern Standard Time", "Australian Central Daylight Time", "Australian Eastern Daylight Time", "Azerbaijan Standard Time", "Azores Standard Time", "Bahia Standard Time", "Bangladesh Standard Time", "Belarus Standard Time", "Canada Central Standard Time", "Cape Verde Standard Time", "Caucasus Standard Time", "Cen. Australia Standard Time", "Central America Standard Time", "Central Asia Standard Time", "Central Brazilian Standard Time", "Central Daylight Time", "Europe Central Time", "European Central Time", "Central Europe Standard Time", "Central Europe Std Time", "Central European Std Time", "Central European Standard Time", "Central Pacific Standard Time", "Central Standard Time", "Central Standard Time (Mexico)", "China Standard Time", "Dateline Standard Time", "E. Africa Standard Time", "E. Australia Standard Time", "E. Europe Standard Time", "E. South America Standard Time", "Eastern Time", "Eastern Daylight Time", "Eastern Standard Time", "Eastern Standard Time (Mexico)", "Egypt Standard Time", "Ekaterinburg Standard Time", "Fiji Standard Time", "FLE Standard Time", "Georgian Standard Time", "GMT Standard Time", "Greenland Standard Time", "Greenwich Standard Time", "GTB Standard Time", "Hawaiian Standard Time", "India Standard Time", "Iran Standard Time", "Israel Standard Time", "Jordan Standard Time", "Kaliningrad Standard Time", "Kamchatka Standard Time", "Korea Standard Time", "Libya Standard Time", "Line Islands Standard Time", "Magadan Standard Time", "Mauritius Standard Time", "Mid-Atlantic Standard Time", "Middle East Standard Time", "Montevideo Standard Time", "Morocco Standard Time", "Mountain Standard Time", "Mountain Standard Time (Mexico)", "Myanmar Standard Time", "N. Central Asia Standard Time", "Namibia Standard Time", "Nepal Standard Time", "New Zealand Standard Time", "Newfoundland Standard Time", "North Asia East Standard Time", "North Asia Standard Time", "North Korea Standard Time", "Pacific SA Standard Time", "Pacific Standard Time", "Pacific Daylight Time", "Pacific Time", "Pacific Standard Time", "Pacific Standard Time (Mexico)", "Pakistan Standard Time", "Paraguay Standard Time", "Romance Standard Time", "Russia Time Zone 1", "Russia Time Zone 2", "Russia Time Zone 3", "Russia Time Zone 4", "Russia Time Zone 5", "Russia Time Zone 6", "Russia Time Zone 7", "Russia Time Zone 8", "Russia Time Zone 9", "Russia Time Zone 10", "Russia Time Zone 11", "Russian Standard Time", "SA Eastern Standard Time", "SA Pacific Standard Time", "SA Western Standard Time", "Samoa Standard Time", "SE Asia Standard Time", "Singapore Standard Time", "Singapore Time", "South Africa Standard Time", "Sri Lanka Standard Time", "Syria Standard Time", "Taipei Standard Time", "Tasmania Standard Time", "Tokyo Standard Time", "Tonga Standard Time", "Turkey Standard Time", "Ulaanbaatar Standard Time", "US Eastern Standard Time", "US Mountain Standard Time", "Mountain", "Venezuela Standard Time", "Vladivostok Standard Time", "W. Australia Standard Time", "W. Central Africa Standard Time", "W. Europe Standard Time", "West Asia Standard Time", "West Pacific Standard Time", "Yakutsk Standard Time", "Pacific Daylight Saving Time", "Austrialian Western Daylight Time", "Austrialian West Daylight Time", "Australian Western Daylight Time", "Australian West Daylight Time", "Colombia Time", "Hong Kong Time", "Central Europe Time", "Central European Time", "Central Europe Summer Time", "Central European Summer Time", "Central Europe Standard Time", "Central European Standard Time", "Central Europe Std Time", "Central European Std Time", "West Coast Time", "West Coast", "Central Time", "Central", "Pacific", "Eastern"); + + public static final String BaseTimeZoneSuffixRegex = "((\\s+|-)(friendly|compatible))?(\\s+|-)time(zone)?"; + + public static final String LocationTimeSuffixRegex = "({BaseTimeZoneSuffixRegex})\\b" + .replace("{BaseTimeZoneSuffixRegex}", BaseTimeZoneSuffixRegex); + + public static final String TimeZoneEndRegex = "({BaseTimeZoneSuffixRegex})$" + .replace("{BaseTimeZoneSuffixRegex}", BaseTimeZoneSuffixRegex); + + public static final List AmbiguousTimezoneList = Arrays.asList("bit", "get", "art", "cast", "eat", "lint", "mat", "most", "west", "vet", "wet", "cot", "pt", "et", "eastern", "pacific", "central", "mountain", "west coast"); + + public static final ImmutableMap AbbrToMinMapping = ImmutableMap.builder() + .put("abst", 180) + .put("acdt", 630) + .put("acst", 570) + .put("act", -10000) + .put("adt", -10000) + .put("aedt", 660) + .put("aest", 600) + .put("aet", 600) + .put("aft", 270) + .put("akdt", -480) + .put("akst", -540) + .put("amst", -10000) + .put("amt", -10000) + .put("aoe", -720) + .put("arbst", 180) + .put("arst", 180) + .put("art", -180) + .put("ast", -10000) + .put("awdt", 540) + .put("awst", 480) + .put("azost", 0) + .put("azot", -60) + .put("azst", 300) + .put("azt", 240) + .put("bit", -720) + .put("bst", -10000) + .put("btt", 360) + .put("cadt", -360) + .put("cast", 480) + .put("cbst", -240) + .put("cbt", -240) + .put("ccst", -360) + .put("cdt", -10000) + .put("cdtm", -360) + .put("cest", 120) + .put("cet", 60) + .put("cot", -300) + .put("cst", -10000) + .put("cstm", -360) + .put("ct", -360) + .put("cvt", -60) + .put("eat", 180) + .put("ect", -10000) + .put("edt", -240) + .put("edtm", -300) + .put("eest", 180) + .put("eet", 120) + .put("egst", 0) + .put("esast", -180) + .put("esat", -180) + .put("est", -300) + .put("estm", -300) + .put("et", -240) + .put("fjst", 780) + .put("fjt", 720) + .put("get", 240) + .put("gmt", 0) + .put("gndt", -180) + .put("gnst", -180) + .put("gst", -10000) + .put("gtbst", 120) + .put("hadt", -540) + .put("hast", -600) + .put("hdt", -540) + .put("hkt", 480) + .put("hst", -600) + .put("irdt", 270) + .put("irkt", 480) + .put("irst", 210) + .put("isdt", 120) + .put("isst", 120) + .put("ist", -10000) + .put("jdt", 120) + .put("jst", 540) + .put("krat", 420) + .put("kst", -10000) + .put("lint", 840) + .put("magst", 720) + .put("magt", 660) + .put("mat", -120) + .put("mdt", -360) + .put("mdtm", -420) + .put("mest", 120) + .put("most", 0) + .put("msk+1", 240) + .put("msk+2", 300) + .put("msk+3", 360) + .put("msk+4", 420) + .put("msk+5", 480) + .put("msk+6", 540) + .put("msk+7", 600) + .put("msk+8", 660) + .put("msk+9", 720) + .put("msk-1", 120) + .put("msk", 180) + .put("mst", -420) + .put("mstm", -420) + .put("mut", 240) + .put("mvst", -180) + .put("myst", 390) + .put("ncast", 420) + .put("ndt", -150) + .put("nmdt", 60) + .put("nmst", 60) + .put("npt", 345) + .put("nst", -210) + .put("nzdt", 780) + .put("nzst", 720) + .put("nzt", 720) + .put("pdst", -420) + .put("pdt", -420) + .put("pdtm", -480) + .put("pett", 720) + .put("pkt", 300) + .put("psast", -240) + .put("psat", -240) + .put("pst", -480) + .put("pstm", -480) + .put("pt", -420) + .put("pyst", -10000) + .put("pyt", -10000) + .put("rst", 60) + .put("saest", -180) + .put("sapst", -300) + .put("sast", 120) + .put("sawst", -240) + .put("sbt", 660) + .put("sgt", 480) + .put("slt", 330) + .put("smst", 780) + .put("snst", 480) + .put("sst", -10000) + .put("tadt", 600) + .put("tast", 600) + .put("tha", 420) + .put("tist", 480) + .put("tost", 840) + .put("tot", 780) + .put("trt", 180) + .put("tst", 540) + .put("ulat", 480) + .put("utc", 0) + .put("vet", -240) + .put("vlat", 600) + .put("wast", 120) + .put("wat", -10000) + .put("west", 60) + .put("wet", 0) + .put("wpst", 600) + .put("yakt", 540) + .put("yekt", 300) + .build(); + + public static final ImmutableMap FullToMinMapping = ImmutableMap.builder() + .put("beijing", 480) + .put("shanghai", 480) + .put("shenzhen", 480) + .put("suzhou", 480) + .put("tianjian", 480) + .put("chengdu", 480) + .put("guangzhou", 480) + .put("wuxi", 480) + .put("xiamen", 480) + .put("chongqing", 480) + .put("shenyang", 480) + .put("china", 480) + .put("redmond", -480) + .put("seattle", -480) + .put("bellevue", -480) + .put("pacific daylight", -420) + .put("pacific", -480) + .put("afghanistan standard", 270) + .put("alaskan standard", -540) + .put("anywhere on earth", -720) + .put("arab standard", 180) + .put("arabian standard", 180) + .put("arabic standard", 180) + .put("argentina standard", -180) + .put("atlantic standard", -240) + .put("aus central standard", 570) + .put("aus eastern standard", 600) + .put("australian eastern", 600) + .put("australian eastern standard", 600) + .put("australian central daylight", 630) + .put("australian eastern daylight", 660) + .put("azerbaijan standard", 240) + .put("azores standard", -60) + .put("bahia standard", -180) + .put("bangladesh standard", 360) + .put("belarus standard", 180) + .put("canada central standard", -360) + .put("cape verde standard", -60) + .put("caucasus standard", 240) + .put("cen. australia standard", 570) + .put("central australia standard", 570) + .put("central america standard", -360) + .put("central asia standard", 360) + .put("central brazilian standard", -240) + .put("central daylight", -10000) + .put("central europe", 60) + .put("central european", 60) + .put("central europe std", 60) + .put("central european std", 60) + .put("central europe standard", 60) + .put("central european standard", 60) + .put("central europe summer", 120) + .put("central european summer", 120) + .put("central pacific standard", 660) + .put("central standard time (mexico)", -360) + .put("central standard", -360) + .put("china standard", 480) + .put("dateline standard", -720) + .put("e. africa standard", 180) + .put("e. australia standard", 600) + .put("e. europe standard", 120) + .put("e. south america standard", -180) + .put("europe central", 60) + .put("european central", 60) + .put("central", -300) + .put("eastern", -240) + .put("eastern daylight", -10000) + .put("eastern standard time (mexico)", -300) + .put("eastern standard", -300) + .put("egypt standard", 120) + .put("ekaterinburg standard", 300) + .put("fiji standard", 720) + .put("fle standard", 120) + .put("georgian standard", 240) + .put("gmt standard", 0) + .put("greenland standard", -180) + .put("greenwich standard", 0) + .put("gtb standard", 120) + .put("hawaiian standard", -600) + .put("india standard", 330) + .put("iran standard", 210) + .put("israel standard", 120) + .put("jordan standard", 120) + .put("kaliningrad standard", 120) + .put("kamchatka standard", 720) + .put("korea standard", 540) + .put("libya standard", 120) + .put("line islands standard", 840) + .put("magadan standard", 660) + .put("mauritius standard", 240) + .put("mid-atlantic standard", -120) + .put("middle east standard", 120) + .put("montevideo standard", -180) + .put("morocco standard", 0) + .put("mountain", -360) + .put("mountain standard", -420) + .put("mountain standard time (mexico)", -420) + .put("myanmar standard", 390) + .put("n. central asia standard", 420) + .put("namibia standard", 60) + .put("nepal standard", 345) + .put("new zealand standard", 720) + .put("newfoundland standard", -210) + .put("north asia east standard", 480) + .put("north asia standard", 420) + .put("north korea standard", 510) + .put("west coast", -420) + .put("pacific sa standard", -240) + .put("pacific standard", -480) + .put("pacific standard time (mexico)", -480) + .put("pakistan standard", 300) + .put("paraguay standard", -240) + .put("romance standard", 60) + .put("russia time zone 1", 120) + .put("russia time zone 2", 180) + .put("russia time zone 3", 240) + .put("russia time zone 4", 300) + .put("russia time zone 5", 360) + .put("russia time zone 6", 420) + .put("russia time zone 7", 480) + .put("russia time zone 8", 540) + .put("russia time zone 9", 600) + .put("russia time zone 10", 660) + .put("russia time zone 11", 720) + .put("russian standard", 180) + .put("sa eastern standard", -180) + .put("sa pacific standard", -300) + .put("sa western standard", -240) + .put("samoa standard", -660) + .put("se asia standard", 420) + .put("singapore standard", 480) + .put("singapore", 480) + .put("south africa standard", 120) + .put("sri lanka standard", 330) + .put("syria standard", 120) + .put("taipei standard", 480) + .put("tasmania standard", 600) + .put("tokyo standard", 540) + .put("tonga standard", 780) + .put("turkey standard", 180) + .put("ulaanbaatar standard", 480) + .put("us eastern standard", -300) + .put("us mountain standard", -420) + .put("venezuela standard", -240) + .put("vladivostok standard", 600) + .put("w. australia standard", 480) + .put("w. central africa standard", 60) + .put("w. europe standard", 0) + .put("western european", 0) + .put("west europe standard", 0) + .put("west europe std", 0) + .put("western europe standard", 0) + .put("western europe summer", 60) + .put("w. europe summer", 60) + .put("western european summer", 60) + .put("west europe summer", 60) + .put("west asia standard", 300) + .put("west pacific standard", 600) + .put("yakutsk standard", 540) + .put("pacific daylight saving", -420) + .put("australian western daylight", 540) + .put("australian west daylight", 540) + .put("austrialian western daylight", 540) + .put("austrialian west daylight", 540) + .put("colombia", -300) + .put("hong kong", 480) + .put("madrid", 60) + .put("bilbao", 60) + .put("seville", 60) + .put("valencia", 60) + .put("malaga", 60) + .put("las Palmas", 60) + .put("zaragoza", 60) + .put("alicante", 60) + .put("alche", 60) + .put("oviedo", 60) + .put("gijón", 60) + .put("avilés", 60) + .build(); + + public static final List MajorLocations = Arrays.asList("Dominican Republic", "Dominica", "Guinea Bissau", "Guinea-Bissau", "Guinea", "Equatorial Guinea", "Papua New Guinea", "New York City", "New York", "York", "Mexico City", "New Mexico", "Mexico", "Aberdeen", "Adelaide", "Anaheim", "Atlanta", "Auckland", "Austin", "Bangkok", "Baltimore", "Baton Rouge", "Beijing", "Belfast", "Birmingham", "Bolton", "Boston", "Bournemouth", "Bradford", "Brisbane", "Bristol", "Calgary", "Canberra", "Cardiff", "Charlotte", "Chicago", "Christchurch", "Colchester", "Colorado Springs", "Coventry", "Dallas", "Denver", "Derby", "Detroit", "Dubai", "Dublin", "Dudley", "Dunedin", "Edinburgh", "Edmonton", "El Paso", "Glasgow", "Gold Coast", "Hamilton", "Hialeah", "Houston", "Ipswich", "Jacksonville", "Jersey City", "Kansas City", "Kingston-upon-Hull", "Leeds", "Leicester", "Lexington", "Lincoln", "Liverpool", "London", "Long Beach", "Los Angeles", "Louisville", "Lubbock", "Luton", "Madison", "Manchester", "Mansfield", "Melbourne", "Memphis", "Mesa", "Miami", "Middlesbrough", "Milan", "Milton Keynes", "Minneapolis", "Montréal", "Montreal", "Nashville", "New Orleans", "Newark", "Newcastle-upon-Tyne", "Newcastle", "Northampton", "Norwich", "Nottingham", "Oklahoma City", "Oldham", "Omaha", "Orlando", "Ottawa", "Perth", "Peterborough", "Philadelphia", "Phoenix", "Plymouth", "Portland", "Portsmouth", "Preston", "Québec City", "Quebec City", "Québec", "Quebec", "Raleigh", "Reading", "Redmond", "Richmond", "Rome", "San Antonio", "San Diego", "San Francisco", "San José", "Santa Ana", "Seattle", "Sheffield", "Southampton", "Southend-on-Sea", "Spokane", "St Louis", "St Paul", "St Petersburg", "St. Louis", "St. Paul", "St. Petersburg", "Stockton-on-Tees", "Stockton", "Stoke-on-Trent", "Sunderland", "Swansea", "Swindon", "Sydney", "Tampa", "Tauranga", "Telford", "Toronto", "Vancouver", "Virginia Beach", "Walsall", "Warrington", "Washington", "Wellington", "Wolverhampton", "Abilene", "Akron", "Albuquerque", "Alexandria", "Allentown", "Amarillo", "Anchorage", "Ann Arbor", "Antioch", "Arlington", "Arvada", "Athens", "Augusta", "Aurora", "Bakersfield", "Beaumont", "Bellevue", "Berkeley", "Billings", "Boise", "Boulder", "Bridgeport", "Broken Arrow", "Brownsville", "Buffalo", "Burbank", "Cambridge", "Cape Coral", "Carlsbad", "Carrollton", "Cary", "Cedar Rapids", "Centennial", "Chandler", "Charleston", "Chattanooga", "Chengdu", "Chesapeake", "Chongqing", "Chula Vista", "Cincinnati", "Clarksville", "Clearwater", "Cleveland", "Clovis", "College Station", "Columbia", "Columbus", "Concord", "Coral Springs", "Corona", "Costa Mesa", "Daly City", "Davenport", "Dayton", "Denton", "Des Moines", "Downey", "Durham", "Edison", "El Cajon", "El Monte", "Elgin", "Elizabeth", "Elk Grove", "Erie", "Escondido", "Eugene", "Evansville", "Everett", "Fairfield", "Fargo", "Farmington Hills", "Fayetteville", "Fontana", "Fort Collins", "Fort Lauderdale", "Fort Wayne", "Fort Worth", "Fremont", "Fresno", "Frisco", "Fullerton", "Gainesville", "Garden Grove", "Garland", "Gilbert", "Glendale", "Grand Prairie", "Grand Rapids", "Green Bay", "Greensboro", "Gresham", "Guangzhou", "Hampton", "Hartford", "Hayward", "Henderson", "High Point", "Hollywood", "Honolulu", "Huntington Beach", "Huntsville", "Independence", "Indianapolis", "Inglewood", "Irvine", "Irving", "Jackson", "Joliet", "Kent", "Killeen", "Knoxville", "Lafayette", "Lakeland", "Lakewood", "Lancaster", "Lansing", "Laredo", "Las Cruces", "Las Vegas", "Lewisville", "Little Rock", "Lowell", "Macon", "McAllen", "McKinney", "Mesquite", "Miami Gardens", "Midland", "Milwaukee", "Miramar", "Mobile", "Modesto", "Montgomery", "Moreno Valley", "Murfreesboro", "Murrieta", "Naperville", "New Haven", "Newport News", "Norfolk", "Norman", "North Charleston", "North Las Vegas", "Norwalk", "Oakland", "Oceanside", "Odessa", "Olathe", "Ontario", "Orange", "Overland Park", "Oxnard", "Palm Bay", "Palmdale", "Pasadena", "Paterson", "Pearland", "Pembroke Pines", "Peoria", "Pittsburgh", "Plano", "Pomona", "Pompano Beach", "Providence", "Provo", "Pueblo", "Rancho Cucamonga", "Reno", "Rialto", "Richardson", "Riverside", "Rochester", "Rockford", "Roseville", "Round Rock", "Sacramento", "Saint Paul", "Salem", "Salinas", "Salt Lake City", "San Bernardino", "San Jose", "San Mateo", "Sandy Springs", "Santa Clara", "Santa Clarita", "Santa Maria", "Santa Rosa", "Savannah", "Scottsdale", "Shanghai", "Shenyang", "Shenzhen", "Shreveport", "Simi Valley", "Sioux Falls", "South Bend", "Springfield", "Stamford", "Sterling Heights", "Sunnyvale", "Surprise", "Suzhou", "Syracuse", "Tacoma", "Tallahassee", "Temecula", "Tempe", "Thornton", "Thousand Oaks", "Tianjing", "Toledo", "Topeka", "Torrance", "Tucson", "Tulsa", "Tyler", "Vallejo", "Ventura", "Victorville", "Visalia", "Waco", "Warren", "Waterbury", "West Covina", "West Jordan", "West Palm Beach", "West Valley City", "Westminster", "Wichita", "Wichita Falls", "Wilmington", "Winston-Salem", "Worcester", "Wuxi", "Xiamen", "Yonkers", "Bentonville", "Afghanistan", "AK", "AL", "Alabama", "Åland", "Åland Islands", "Alaska", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "AR", "Argentina", "Arizona", "Arkansas", "Armenia", "Aruba", "Australia", "Austria", "AZ", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bonaire", "Bosnia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "CA", "Cabo Verde", "California", "Cambodia", "Cameroon", "Canada", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "CO", "Cocos Islands", "Colombia", "Colorado", "Comoros", "Congo", "Congo (DRC)", "Connecticut", "Cook Islands", "Costa Rica", "Côte d’Ivoire", "Croatia", "CT", "Cuba", "Curaçao", "Cyprus", "Czechia", "DE", "Delaware", "Denmark", "Djibouti", "Ecuador", "Egypt", "El Salvador", "Eritrea", "Estonia", "eSwatini", "Ethiopia", "Falkland Islands", "Falklands", "Faroe Islands", "Fiji", "Finland", "FL", "Florida", "France", "French Guiana", "French Polynesia", "French Southern Territories", "FYROM", "GA", "Gabon", "Gambia", "Georgia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guyana", "Haiti", "Hawaii", "Herzegovina", "HI", "Honduras", "Hong Kong", "Hungary", "IA", "Iceland", "ID", "Idaho", "IL", "Illinois", "IN", "India", "Indiana", "Indonesia", "Iowa", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Ivory Coast", "Jamaica", "Jan Mayen", "Japan", "Jersey", "Jordan", "Kansas", "Kazakhstan", "Keeling Islands", "Kentucky", "Kenya", "Kiribati", "Korea", "Kosovo", "KS", "Kuwait", "KY", "Kyrgyzstan", "LA", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Louisiana", "Luxembourg", "MA", "Macao", "Macedonia", "Madagascar", "Maine", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Maryland", "Massachusetts", "Mauritania", "Mauritius", "Mayotte", "MD", "ME", "MI", "Michigan", "Micronesia", "Minnesota", "Mississippi", "Missouri", "MN", "MO", "Moldova", "Monaco", "Mongolia", "Montana", "Montenegro", "Montserrat", "Morocco", "Mozambique", "MS", "MT", "Myanmar", "Namibia", "Nauru", "NC", "ND", "NE", "Nebraska", "Nepal", "Netherlands", "Nevada", "New Caledonia", "New Hampshire", "New Jersey", "New Zealand", "NH", "Nicaragua", "Niger", "Nigeria", "Niue", "NJ", "NM", "Norfolk Island", "North Carolina", "North Dakota", "North Korea", "Northern Mariana Islands", "Norway", "NV", "NY", "OH", "Ohio", "OK", "Oklahoma", "Oman", "OR", "Oregon", "PA", "Pakistan", "Palau", "Palestinian Authority", "Panama", "Paraguay", "Pennsylvania", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Réunion", "Rhode Island", "RI", "Romania", "Russia", "Rwanda", "Saba", "Saint Barthélemy", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "São Tomé and Príncipe", "Saudi Arabia", "SC", "SD", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Sint Eustatius", "Sint Maarten", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Carolina", "South Dakota", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Tennessee", "Texas", "Thailand", "Timor-Leste", "TN", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "TX", "U.S. Outlying Islands", "US Outlying Islands", "U.S. Virgin Islands", "US Virgin Islands", "Uganda", "UK", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "US", "USA", "UT", "Utah", "Uzbekistan", "VA", "Vanuatu", "Vatican City", "Venezuela", "Vermont", "Vietnam", "Virginia", "VT", "WA", "Wallis and Futuna", "West Virginia", "WI", "Wisconsin", "WV", "WY", "Wyoming", "Yemen", "Zambia", "Zimbabwe", "Paris", "Tokyo", "Shanghai", "Sao Paulo", "Rio de Janeiro", "Rio", "Brasília", "Brasilia", "Recife", "Milan", "Mumbai", "Moscow", "Frankfurt", "Munich", "Berlim", "Madrid", "Lisbon", "Warsaw", "Johannesburg", "Seoul", "Istanbul", "Kuala Kumpur", "Jakarta", "Amsterdam", "Brussels", "Valencia", "Seville", "Bilbao", "Malaga", "Las Palmas", "Zaragoza", "Alicante", "Elche", "Oviedo", "Gijón", "Avilés", "West Coast", "Central", "Pacific", "Eastern", "Mountain"); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java new file mode 100644 index 000000000..0ab5214d3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java @@ -0,0 +1,1234 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class FrenchDateTime { + + public static final String LangMarker = "Fre"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?au|et|(jusqu')?[aà]|avant|--|-|—|——)"; + + public static final String RangeConnectorRegex = "(?de la|au|[aà]|et(\\s*la)?|--|-|—|——)"; + + public static final String RelativeRegex = "(?prochaine?|de|du|ce(tte)?|l[ae]|derni[eè]re|pr[eé]c[eé]dente|au\\s+cours+(de|du\\s*))"; + + public static final String StrictRelativeRegex = "(?prochaine?|derni[eè]re|pr[eé]c[eé]dente|au\\s+cours+(de|du\\s*))"; + + public static final String NextSuffixRegex = "(?prochaines?|prochain|suivante)\\b"; + + public static final String PastSuffixRegex = "(?derni[eè]re?|pr[eé]c[eé]dente)\\b"; + + public static final String ThisPrefixRegex = "(?ce(tte)?|au\\s+cours+(du|de))\\b"; + + public static final String RangePrefixRegex = "(du|depuis|des?|entre)"; + + public static final String DayRegex = "(?01|02|03|04|05|06|07|08|09|10|11e?|12e?|13e?|14e?|15e?|16e?|17e?|18e?|19e?|1er|1|21e?|20e?|22e?|23e?|24e?|25e?|26e?|27e?|28e?|29e?|2e?|30e?|31e?|3e?|4e?|5e?|6e?|7e?|8e?|9e?)(?=\\b|t)"; + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)\\b"; + + public static final String SpecialDescRegex = "(p\\b)"; + + public static final String AmDescRegex = "(h\\b|{BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "(h\\b|{BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "(h\\b|{BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(?{AmPmDescRegex}|{AmDescRegex}|{PmDescRegex}|{SpecialDescRegex})" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{AmPmDescRegex}", AmPmDescRegex) + .replace("{SpecialDescRegex}", SpecialDescRegex); + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d)))\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String FullTextYearRegex = "^[\\*]"; + + public static final String YearRegex = "({BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String WeekDayRegex = "(?dimanche|lundi|mardi|mercredi|jeudi|vendredi|samedi|lun(\\.)?|mar(\\.)?|mer(\\.)?|jeu(\\.)?|ven(\\.)?|sam(\\.)?|dim(\\.)?)"; + + public static final String RelativeMonthRegex = "(?({ThisPrefixRegex}\\s+mois)|(mois\\s+{PastSuffixRegex})|(mois\\s+{NextSuffixRegex}))\\b" + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String WrittenMonthRegex = "(?avril|avr(\\.)?|ao[uû]t|d[eé]cembre|d[eé]c(\\.)?|f[eé]vrier|f[eé]vr?(\\.)?|janvier|janv?(\\.)?|juillet|jui?[ln](\\.)?|mars?(\\.)?|mai|novembre|nov(\\.)?|octobre|oct(\\.)?|septembre|sept?(\\.)?)"; + + public static final String MonthSuffixRegex = "(?(en\\s*|le\\s*|de\\s*|dans\\s*)?({RelativeMonthRegex}|{WrittenMonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{WrittenMonthRegex}", WrittenMonthRegex); + + public static final String DateUnitRegex = "(?(l')?ann[eé]es?|an|mois|semaines?|journ[eé]es?|jours?)\\b"; + + public static final String SimpleCasesRegex = "\\b((d[ue])|entre\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b((d[ue]|entre)\\s+)?{MonthSuffixRegex}\\s+((d[ue]|entre)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+(entre|d[ue]\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{YearRegex}", YearRegex); + + public static final String BetweenRegex = "\\b(entre\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String YearWordRegex = "\\b(?l'ann[ée]e)\\b"; + + public static final String MonthWithYear = "\\b({WrittenMonthRegex}(\\s*),?(\\s+de)?(\\s*)({YearRegex}|{TwoDigitYearRegex}|(?cette)\\s*{YearWordRegex})|{YearWordRegex}\\s*({PastSuffixRegex}|{NextSuffixRegex}))" + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex) + .replace("{YearWordRegex}", YearWordRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String OneWordPeriodRegex = "\\b(({RelativeRegex}\\s+)?{WrittenMonthRegex}|(la\\s+)?(weekend|(fin de )?semaine|week-end|mois|ans?|l'année)\\s+{StrictRelativeRegex}|{RelativeRegex}\\s+(weekend|(fin de )?semaine|week-end|mois|ans?|l'année)|weekend|week-end|(mois|l'année))\\b" + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String MonthNumWithYear = "({YearRegex}(\\s*)[/\\-\\.](\\s*){MonthNumRegex})|({MonthNumRegex}(\\s*)[/\\-](\\s*){YearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "(?(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4|cinqi[èe]me|5)\\s+semaine(\\s+de)?\\s+{MonthSuffixRegex})" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String WeekOfYearRegex = "(?(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4|cinqi[èe]me|5)\\s+semaine(\\s+de)?\\s+({YearRegex}|{RelativeRegex}\\s+ann[ée]e))" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterRegex = "(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4)\\s+quart(\\s+de|\\s*,\\s*)?\\s+({YearRegex}|{RelativeRegex}\\s+l'ann[eé]e)" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String QuarterRegexYearFront = "({YearRegex}|l'année\\s+({PastSuffixRegex}|{NextSuffixRegex})|{RelativeRegex}\\s+ann[eé]e)\\s+(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4)\\s+quarts" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String AllHalfYearRegex = "^[.]"; + + public static final String PrefixDayRegex = "\\b((?t[ôo]t\\sdans)|(?au\\smilieu\\sde)|(?tard\\sdans))(\\s+la\\s+journ[ée]e)?$"; + + public static final String CenturySuffixRegex = "^[.]"; + + public static final String SeasonRegex = "\\b((printemps|été|automne|hiver)+\\s*({NextSuffixRegex}|{PastSuffixRegex}))|(?({RelativeRegex}\\s+)?(?printemps|[ée]t[ée]|automne|hiver)((\\s+de|\\s*,\\s*)?\\s+({YearRegex}|{RelativeRegex}\\s+l'ann[eé]e))?)\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex); + + public static final String WhichWeekRegex = "\\b(semaine)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "(semaine)(\\s*)(de)"; + + public static final String MonthOfRegex = "(mois)(\\s*)(de)"; + + public static final String MonthRegex = "(?avril|avr(\\.)?|ao[uû]t|d[eé]cembre|d[eé]c(\\.)?|f[eé]vrier|f[eé]vr?(\\.)?|janvier|janv?(\\.)?|juillet|jui?[ln](\\.)?|mars?(\\.)?|mai|novembre|nov(\\.)?|octobre|oct(\\.)?|septembre|sept?(\\.)?)"; + + public static final String OnRegex = "(?<=\\b(en|sur\\s*l[ea]|sur)\\s+)({DayRegex}s?)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String RelaxedOnRegex = "(?<=\\b(en|le|dans|sur\\s*l[ea]|du|sur)\\s+)((?10e|11e|12e|13e|14e|15e|16e|17e|18e|19e|1er|20e|21e|22e|23e|24e|25e|26e|27e|28e|29e|2e|30e|31e|3e|4e|5e|6e|7e|8e|9e)s?)\\b"; + + public static final String ThisRegex = "\\b((cette(\\s*semaine)?\\s+){WeekDayRegex})|({WeekDayRegex}(\\s+cette\\s*semaine))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String LastDateRegex = "\\b(({WeekDayRegex}(\\s*(de)?\\s*la\\s*semaine\\s+{PastSuffixRegex}))|({WeekDayRegex}(\\s+{PastSuffixRegex})))\\b" + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String NextDateRegex = "\\b(({WeekDayRegex}(\\s+{NextSuffixRegex}))|({WeekDayRegex}(\\s*(de)?\\s*la\\s*semaine\\s+{NextSuffixRegex})))\\b" + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDayRegex = "\\b(avant[\\s|-]hier|apr[eè]s(-demain|\\s*demain)|(le\\s)?jour suivant|(le\\s+)?dernier jour|hier|lendemain|demain|(de\\s)?la journ[ée]e|aujourd'hui)\\b"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String StrictWeekDay = "\\b(?dim(anche)?|lun(di)?|mar(di)?|mer(credi)?|jeu(di)?|ven(dredi)?|sam(edi)?)s?\\b"; + + public static final String SetWeekDayRegex = "\\b(?le\\s+)?(?matin([ée]e)?|apr[eè]s-midi|soir([ée]e)?|dimanche|lundi|mardi|mercredi|jeudi|vendredi|samedi)s\\b"; + + public static final String WeekDayOfMonthRegex = "(?(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4|cinqi[èe]me|5)\\s+{WeekDayRegex}\\s+{MonthSuffixRegex})" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "^[.]"; + + public static final String AmbiguousRangeModifierPrefix = "^[.]"; + + public static final String NumberEndingPattern = "^[.]"; + + public static final String SpecialDate = "(?<=\\b(au|le)\\s+){DayRegex}(?!:)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String DateYearRegex = "(?{YearRegex}|{TwoDigitYearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex); + + public static final String DateExtractor1 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{MonthRegex}\\s*[/\\\\\\.\\-]?\\s*{DayRegex}\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor2 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}(\\s+|\\s*,\\s*|\\s+){MonthRegex}\\s*[\\.\\-]?\\s*{DateYearRegex}\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor3 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?(?(l')?ann[eé]e(s)?|mois|semaines?)\\b"; + + public static final String HourNumRegex = "\\b(?zero|[aá]\\s+une?|deux|trois|quatre|cinq|six|sept|huit|neuf|onze|douze|treize|quatorze|quinze|dix-six|dix-sept|dix-huit|dix-neuf|vingt|vingt-et-un|vingt-deux|vingt-trois|dix)\\b"; + + public static final String MinuteNumRegex = "(?un|deux|trois|quatre|cinq|six|sept|huit|neuf|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt|trente|quarante|cinquante|dix)"; + + public static final String DeltaMinuteNumRegex = "(?un|deux|trois|quatre|cinq|six|sept|huit|neuf|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt|trente|quarante|cinquante|dix)"; + + public static final String OclockRegex = "(?heures?|h)"; + + public static final String PmRegex = "(?(dans l'\\s*)?apr[eè]s(\\s*|-)midi|(du|ce|de|le)\\s*(soir([ée]e)?)|(dans l[ea]\\s+)?(nuit|soir[eé]e))"; + + public static final String AmRegex = "(?(du|de|ce|(du|de|dans)\\s*l[ea]|le)?\\s*matin[ée]e|(du|de|ce|dans l[ea]|le)?\\s*matin)"; + + public static final String LessThanOneHour = "(?(une\\s+)?quart|trois quart(s)?|demie( heure)?|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutes?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutes?|mins?)))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String WrittenTimeRegex = "(?{HourNumRegex}\\s+({MinuteNumRegex}|(?vingt|trente|quarante|cinquante)\\s+{MinuteNumRegex}))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimePrefix = "(?(heures\\s*et\\s+{LessThanOneHour}|et {LessThanOneHour}|{LessThanOneHour} [àa]))" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?{AmRegex}|{PmRegex}|{OclockRegex})" + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex})" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String MidnightRegex = "(?minuit)"; + + public static final String CommonDatePrefixRegex = "^[\\.]"; + + public static final String MorningRegex = "(?matin([ée]e)?)"; + + public static final String AfternoonRegex = "(?(d'|l')?apr[eè]s(-|\\s*)midi)"; + + public static final String MidmorningRegex = "(?milieu\\s*d[ue]\\s*{MorningRegex})" + .replace("{MorningRegex}", MorningRegex); + + public static final String MiddayRegex = "(?milieu(\\s*|-)d[eu]\\s*(jour|midi)|apr[eè]s(-|\\s*)midi)"; + + public static final String MidafternoonRegex = "(?milieu\\s*d'+{AfternoonRegex})" + .replace("{AfternoonRegex}", AfternoonRegex); + + public static final String MidTimeRegex = "(?({MidnightRegex}|{MidmorningRegex}|{MidafternoonRegex}|{MiddayRegex}))" + .replace("{MidnightRegex}", MidnightRegex) + .replace("{MidmorningRegex}", MidmorningRegex) + .replace("{MidafternoonRegex}", MidafternoonRegex) + .replace("{MiddayRegex}", MiddayRegex); + + public static final String AtRegex = "\\b(((?<=\\b[àa]\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}|{MidTimeRegex}))|{MidTimeRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String IshRegex = "\\b(peu\\s*pr[èe]s\\s*{BaseDateTime.HourRegex}|peu\\s*pr[èe]s\\s*{WrittenTimeRegex}|peu\\s*pr[èe]s\\s*[àa]\\s*{BaseDateTime.HourRegex}|peu pr[èe]s midi)\\b" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{WrittenTimeRegex}", WrittenTimeRegex); + + public static final String TimeUnitRegex = "(?heures?|hrs?|h|minutes?|mins?|secondes?|secs?)\\b"; + + public static final String RestrictedTimeUnitRegex = "(?huere|minute)\\b"; + + public static final String ConnectNumRegex = "{BaseDateTime.HourRegex}(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)\\s*{DescRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String FivesRegex = "(?(quinze|vingt(\\s*|-*(cinq))?|trente(\\s*|-*(cinq))?|quarante(\\s*|-*(cinq))??|cinquante(\\s*|-*(cinq))?|dix|cinq))\\b"; + + public static final String PeriodHourNumRegex = "(?vingt-et-un|vingt-deux|vingt-trois|vingt-quatre|zero|une|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt)"; + + public static final String TimeRegex1 = "\\b({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\s*{DescRegex}(\\s+{TimePrefix})?\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "\\b{BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex}(\\s*{DescRegex})(\\s+{TimePrefix})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex4 = "\\b{BasicTime}(\\s*{DescRegex})?(\\s+{TimePrefix})?\\s+{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex5 = "\\b{BasicTime}((\\s*{DescRegex})(\\s+{TimePrefix})?|\\s+{TimePrefix})" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex6 = "{BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffix}\\s+[àa]\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = "\\b{TimeSuffix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b{PeriodHourNumRegex}\\s+{FivesRegex}((\\s*{DescRegex})|\\b)" + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{FivesRegex}", FivesRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex10 = "\\b{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?(\\s+{TimePrefix})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String HourRegex = "\\b{BaseDateTime.HourRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String PeriodDescRegex = "(?pm|am|p\\.m\\.|a\\.m\\.|p)"; + + public static final String PeriodPmRegex = "(?dans l'apr[eè]s-midi|ce soir|d[eu] soir|dans l[ea] soir[eé]e|dans la nuit|d[eu] soir[ée]e)s?"; + + public static final String PeriodAmRegex = "(?d[eu] matin|matin([ée]e)s?"; + + public static final String PureNumFromTo = "((du|depuis|des?)\\s+)?({HourRegex}|{PeriodHourNumRegex})(\\s*(?{PeriodDescRegex}))?\\s*{TillRegex}\\s*({HourRegex}|{PeriodHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{PeriodDescRegex})?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{PeriodDescRegex}", PeriodDescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String PureNumBetweenAnd = "(entre\\s+)({HourRegex}|{PeriodHourNumRegex})(\\s*(?{PeriodDescRegex}))?\\s*{RangeConnectorRegex}\\s*({HourRegex}|{PeriodHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{PeriodDescRegex})?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{PeriodDescRegex}", PeriodDescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String SpecificTimeFromTo = "^[.]"; + + public static final String SpecificTimeBetweenAnd = "^[.]"; + + public static final String PrepositionRegex = "(?^([aà](\\s+?la)?|en|sur(\\s*l[ea])?|de)$)"; + + public static final String TimeOfDayRegex = "\\b(?((((dans\\s+(l[ea])?\\s+)?((?d[eé]but(\\s+|-)|t[oô]t(\\s+|-)(l[ea]\\s*)?)|(?fin\\s*|fin de(\\s+(la)?)|tard\\s*))?(matin([ée]e)?|((d|l)?'?)apr[eè]s[-|\\s*]midi|nuit|soir([eé]e)?)))|(((\\s+(l[ea])?\\s+)?)jour(n[eé]e)?))s?)\\b"; + + public static final String SpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{TimeOfDayRegex})|({TimeOfDayRegex}\\s*({NextSuffixRegex}))\\b|\\bsoir|\\bdu soir)s?\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?){TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String NowRegex = "\\b(?(ce\\s+)?moment|maintenant|d[eè]s que possible|dqp|r[eé]cemment|auparavant)\\b"; + + public static final String SuffixRegex = "^\\s*((dans\\s+l[ea]\\s+)|(en\\s+)|(d(u|\\'))?(matin([eé]e)?|apr[eè]s-midi|soir[eé]e|nuit))\\b"; + + public static final String DateTimeTimeOfDayRegex = "\\b(?matin([eé]e)?|apr[eè]s-midi|nuit|soir)\\b"; + + public static final String DateTimeSpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{DateTimeTimeOfDayRegex})\\b|\\b(ce(tte)?\\s+)(soir|nuit))\\b" + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String TimeOfTodayAfterRegex = "^\\s*(,\\s*)?(en|dans|du\\s+)?{DateTimeSpecificTimeOfDayRegex}" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String TimeOfTodayBeforeRegex = "{DateTimeSpecificTimeOfDayRegex}(\\s*,)?(\\s+([àa]|pour))?\\s*$" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayAfterRegex = "({HourNumRegex}|{BaseDateTime.HourRegex})\\s*(,\\s*)?(en|[àa]\\s+)?{DateTimeSpecificTimeOfDayRegex}" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayBeforeRegex = "{DateTimeSpecificTimeOfDayRegex}(\\s*,)?(\\s+([àa]|vers))?\\s*({HourNumRegex}|{BaseDateTime.HourRegex})" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String SpecificEndOfRegex = "(la\\s+)?fin(\\s+de\\s*|\\s*de*l[ea])?\\s*$"; + + public static final String UnspecificEndOfRegex = "^[.]"; + + public static final String UnspecificEndOfRangeRegex = "^[.]"; + + public static final String PeriodTimeOfDayRegex = "\\b((dans\\s+(le)?\\s+)?((?d[eé]but(\\s+|-|d[ue]|de la)|t[oô]t)|(?tard\\s*|fin(\\s+|-|d[eu])?))?(?matin|((d|l)?'?)apr[eè]s-midi|nuit|soir([eé]e)?))\\b"; + + public static final String PeriodSpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{PeriodTimeOfDayRegex})\\b|\\b(ce(tte)?\\s+)(soir|nuit))\\b" + .replace("{PeriodTimeOfDayRegex}", PeriodTimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String PeriodTimeOfDayWithDateRegex = "\\b(({TimeOfDayRegex}))\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String LessThanRegex = "^[.]"; + + public static final String MoreThanRegex = "^[.]"; + + public static final String DurationUnitRegex = "(?ann[eé]es?|ans?|mois|semaines?|jours?|heures?|hrs?|h|minutes?|mins?|secondes?|secs?|journ[eé]e)\\b"; + + public static final String SuffixAndRegex = "(?\\s*(et)\\s+(une?\\s+)?(?demi|quart))"; + + public static final String PeriodicRegex = "\\b(?quotidien(ne)?|journellement|mensuel(le)?|jours?|hebdomadaire|bihebdomadaire|annuel(lement)?)\\b"; + + public static final String EachUnitRegex = "(?(chaque|toutes les|tous les)(?\\s+autres)?\\s*{DurationUnitRegex})" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String EachPrefixRegex = "\\b(?(chaque|tous les|(toutes les))\\s*$)"; + + public static final String SetEachRegex = "\\b(?(chaque|tous les|(toutes les))\\s*)"; + + public static final String SetLastRegex = "(?prochain|dernier|derni[eè]re|pass[ée]s|pr[eé]c[eé]dent|courant|en\\s*cours)"; + + public static final String EachDayRegex = "^\\s*(chaque|tous les)\\s*(jour|jours)\\b"; + + public static final String DurationFollowedUnit = "^\\s*{SuffixAndRegex}?(\\s+|-)?{DurationUnitRegex}" + .replace("{SuffixAndRegex}", SuffixAndRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String NumberCombinedWithDurationUnit = "\\b(?\\d+(\\.\\d*)?)(-)?{DurationUnitRegex}" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String AnUnitRegex = "\\b(((?demi\\s+)?(-)\\s+{DurationUnitRegex}))" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String DuringRegex = "^[.]"; + + public static final String AllRegex = "\\b(?toute\\s(l['ea])\\s?(?ann[eé]e|mois|semaines?|jours?|journ[eé]e))\\b"; + + public static final String HalfRegex = "((une?\\s*)|\\b)(?demi?(\\s*|-)+(?ann[eé]e|ans?|mois|semaine|jour|heure))\\b"; + + public static final String ConjunctionRegex = "\\b((et(\\s+de|pour)?)|avec)\\b"; + + public static final String HolidayRegex1 = "\\b(?vendredi saint|mercredi des cendres|p[aâ]ques|l'action de gr[âa]ce|mardi gras|la saint-sylvestre|la saint sylvestre|la saint-valentin|la saint valentin|nouvel an chinois|nouvel an|r[eé]veillon de nouvel an|jour de l'an|premier-mai|ler-mai|1-mai|poisson d'avril|r[eé]veillon de no[eë]l|veille de no[eë]l|noël|noel|thanksgiving|halloween|yuandan)(\\s+((d[ue]\\s+|d'))?({YearRegex}|({ThisPrefixRegex}\\s+)ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String HolidayRegex2 = "\\b(?martin luther king|martin luther king jr|toussaint|st patrick|st george|cinco de mayo|l'ind[eé]pendance(\\s+am[eé]ricaine)?|guy fawkes)(\\s+(de\\s+)?({YearRegex}|{ThisPrefixRegex}\\s+ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String HolidayRegex3 = "(?(jour\\s*(d[eu]|des)\\s*(canberra|p[aâ]ques|colomb|bastille|la prise de la bastille|thanks\\s*giving|bapt[êe]me|nationale|d'armistice|inaugueration|marmotte|assomption|femme|comm[ée]moratif)))(\\s+(de\\s+)?({YearRegex}|{ThisPrefixRegex}\\s+ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String HolidayRegex4 = "(?(f[eê]te\\s*(d[eu]|des)\\s*)(travail|m[eè]res?|p[eè]res?))(\\s+(de\\s+)?({YearRegex}|{ThisPrefixRegex}\\s+ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String DateTokenPrefix = "le "; + + public static final String TimeTokenPrefix = "à "; + + public static final String TokenBeforeDate = "le "; + + public static final String TokenBeforeTime = "à "; + + public static final String AMTimeRegex = "(?matin([ée]e)?)"; + + public static final String PMTimeRegex = "\\b(?(d'|l')?apr[eè]s-midi|nuit|((\\s*ce|du)\\s+)?soir)\\b"; + + public static final String BeforeRegex = "\\b(avant)\\b"; + + public static final String BeforeRegex2 = "\\b(entre\\s*(le|la(s)?)?)\\b"; + + public static final String AfterRegex = "\\b(apres)\\b"; + + public static final String SinceRegex = "\\b(depuis)\\b"; + + public static final String AroundRegex = "^[.]"; + + public static final String AgoPrefixRegex = "\\b(y a)\\b"; + + public static final String LaterRegex = "\\b(plus tard)\\b"; + + public static final String AgoRegex = "^[.]"; + + public static final String BeforeAfterRegex = "^[.]"; + + public static final String InConnectorRegex = "\\b(dans|en|sur)\\b"; + + public static final String SinceYearSuffixRegex = "^[.]"; + + public static final String WithinNextPrefixRegex = "^[.]"; + + public static final String TodayNowRegex = "\\b(aujourd'hui|maintenant)\\b"; + + public static final String MorningStartEndRegex = "(^(matin))|((matin)$)"; + + public static final String AfternoonStartEndRegex = "(^((d'|l')?apr[eè]s-midi))|(((d'|l')?apr[eè]s-midi)$)"; + + public static final String EveningStartEndRegex = "(^(soir[ée]e|soir))|((soir[ée]e|soir)$)"; + + public static final String NightStartEndRegex = "(^(nuit))|((nuit)$)"; + + public static final String InexactNumberRegex = "\\b(quel qu[ée]s|quelqu[ée]s?|plusieurs?|divers)\\b"; + + public static final String InexactNumberUnitRegex = "({InexactNumberRegex})\\s+({DurationUnitRegex})" + .replace("{InexactNumberRegex}", InexactNumberRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String RelativeTimeUnitRegex = "(((({ThisPrefixRegex})?)\\s+({TimeUnitRegex}(\\s*{NextSuffixRegex}|{PastSuffixRegex})?))|((le))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{TimeUnitRegex}", TimeUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String RelativeDurationUnitRegex = "((\\b({DurationUnitRegex})(\\s+{NextSuffixRegex}|{PastSuffixRegex})?)|((le|my))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String ReferenceDatePeriodRegex = "^[.]"; + + public static final String UpcomingPrefixRegex = ".^"; + + public static final String NextPrefixRegex = ".^"; + + public static final String PastPrefixRegex = ".^"; + + public static final String PreviousPrefixRegex = ".^"; + + public static final String RelativeDayRegex = "\\b(((la\\s+)?{RelativeRegex}\\s+journ[ée]e))\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String ConnectorRegex = "^(,|pour|t|vers)$"; + + public static final String ConnectorAndRegex = "\\b(et\\s*(le|las?)?)\\b.+"; + + public static final String FromRegex = "((de|du)?)$"; + + public static final String FromRegex2 = "((depuis|de)(\\s*las?)?)$"; + + public static final String FromToRegex = "\\b(du|depuis|des?).+(au|à|a)\\b.+"; + + public static final String SingleAmbiguousMonthRegex = "^(le\\s+)?(may|march)$"; + + public static final String UnspecificDatePeriodRegex = "^[.]"; + + public static final String PrepositionSuffixRegex = "\\b(du|de|[àa]|vers|dans)$"; + + public static final String FlexibleDayRegex = "(?([A-Za-z]+\\s)?[A-Za-z\\d]+)"; + + public static final String ForTheRegex = "\\b(((pour le {FlexibleDayRegex})|(dans (le\\s+)?{FlexibleDayRegex}(?<=(st|nd|rd|th))))(?\\s*(,|\\.|!|\\?|$)))" + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayOfMonthRegex = "\\b{WeekDayRegex}\\s+(le\\s+{FlexibleDayRegex})\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayRegex = "\\b{WeekDayRegex}\\s+(?!(the)){DayRegex}(?!([-:]|(\\s+({AmDescRegex}|{PmDescRegex}|{OclockRegex}))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String RestOfDateRegex = "\\b(reste|fin)\\s+(d[eu]\\s+)?((le|ce(tte)?)\\s+)?(?semaine|mois|l'ann[ée]e)\\b"; + + public static final String RestOfDateTimeRegex = "\\b(reste|fin)\\s+(d[eu]\\s+)?((le|ce(tte)?)\\s+)?(?jour)\\b"; + + public static final String LaterEarlyPeriodRegex = "^[.]"; + + public static final String WeekWithWeekDayRangeRegex = "^[.]"; + + public static final String GeneralEndingRegex = "^[.]"; + + public static final String MiddlePauseRegex = "^[.]"; + + public static final String DurationConnectorRegex = "^[.]"; + + public static final String PrefixArticleRegex = "^[\\.]"; + + public static final String OrRegex = "^[.]"; + + public static final String YearPlusNumberRegex = "^[.]"; + + public static final String NumberAsTimeRegex = "^[.]"; + + public static final String TimeBeforeAfterRegex = "^[.]"; + + public static final String DateNumberConnectorRegex = "^[.]"; + + public static final String CenturyRegex = "^[.]"; + + public static final String DecadeRegex = "^[.]"; + + public static final String DecadeWithCenturyRegex = "^[.]"; + + public static final String RelativeDecadeRegex = "^[.]"; + + public static final String YearSuffix = "(,?\\s*({DateYearRegex}|{FullTextYearRegex}))" + .replace("{DateYearRegex}", DateYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String SuffixAfterRegex = "^[.]"; + + public static final String YearPeriodRegex = "^[.]"; + + public static final String FutureSuffixRegex = "^[.]"; + + public static final String ComplexDatePeriodRegex = "^[.]"; + + public static final String AmbiguousPointRangeRegex = "^(mar\\.?)$"; + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("annees", "Y") + .put("annee", "Y") + .put("an", "Y") + .put("ans", "Y") + .put("mois", "MON") + .put("semaines", "W") + .put("semaine", "W") + .put("journees", "D") + .put("journee", "D") + .put("jour", "D") + .put("jours", "D") + .put("heures", "H") + .put("heure", "H") + .put("hrs", "H") + .put("hr", "H") + .put("h", "H") + .put("minutes", "M") + .put("minute", "M") + .put("mins", "M") + .put("min", "M") + .put("secondes", "S") + .put("seconde", "S") + .put("secs", "S") + .put("sec", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("annees", 31536000L) + .put("annee", 31536000L) + .put("l'annees", 31536000L) + .put("l'annee", 31536000L) + .put("an", 31536000L) + .put("ans", 31536000L) + .put("mois", 2592000L) + .put("semaines", 604800L) + .put("semaine", 604800L) + .put("journees", 86400L) + .put("journee", 86400L) + .put("jour", 86400L) + .put("jours", 86400L) + .put("heures", 3600L) + .put("heure", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutes", 60L) + .put("minute", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("secondes", 1L) + .put("seconde", 1L) + .put("secs", 1L) + .put("sec", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("printemps", "SP") + .put("été", "SU") + .put("automne", "FA") + .put("hiver", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("premier", 1) + .put("1er", 1) + .put("deuxième", 2) + .put("2e", 2) + .put("troisième", 3) + .put("troisieme", 3) + .put("3e", 3) + .put("quatrième", 4) + .put("4e", 4) + .put("cinqième", 5) + .put("5e", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("lundi", 1) + .put("mardi", 2) + .put("mercredi", 3) + .put("jeudi", 4) + .put("vendredi", 5) + .put("samedi", 6) + .put("dimanche", 0) + .put("lun", 1) + .put("mar", 2) + .put("mer", 3) + .put("jeu", 4) + .put("ven", 5) + .put("sam", 6) + .put("dim", 0) + .put("lun.", 1) + .put("mar.", 2) + .put("mer.", 3) + .put("jeu.", 4) + .put("ven.", 5) + .put("sam.", 6) + .put("dim.", 0) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("janvier", 1) + .put("fevrier", 2) + .put("février", 2) + .put("mars", 3) + .put("mar", 3) + .put("mar.", 3) + .put("avril", 4) + .put("avr", 4) + .put("avr.", 4) + .put("mai", 5) + .put("juin", 6) + .put("jun", 6) + .put("jun.", 6) + .put("juillet", 7) + .put("aout", 8) + .put("août", 8) + .put("septembre", 9) + .put("octobre", 10) + .put("novembre", 11) + .put("decembre", 12) + .put("décembre", 12) + .put("janv", 1) + .put("janv.", 1) + .put("jan", 1) + .put("jan.", 1) + .put("fevr", 2) + .put("fevr.", 2) + .put("févr.", 2) + .put("févr", 2) + .put("fev", 2) + .put("fev.", 2) + .put("juil", 7) + .put("jul", 7) + .put("jul.", 7) + .put("sep", 9) + .put("sep.", 9) + .put("sept.", 9) + .put("sept", 9) + .put("oct", 10) + .put("oct.", 10) + .put("nov", 11) + .put("nov.", 11) + .put("dec", 12) + .put("dec.", 12) + .put("déc.", 12) + .put("déc", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("zero", 0) + .put("un", 1) + .put("une", 1) + .put("a", 1) + .put("deux", 2) + .put("trois", 3) + .put("quatre", 4) + .put("cinq", 5) + .put("six", 6) + .put("sept", 7) + .put("huit", 8) + .put("neuf", 9) + .put("dix", 10) + .put("onze", 11) + .put("douze", 12) + .put("treize", 13) + .put("quatorze", 14) + .put("quinze", 15) + .put("seize", 16) + .put("dix-sept", 17) + .put("dix-huit", 18) + .put("dix-neuf", 19) + .put("vingt-et-un", 21) + .put("vingt et un", 21) + .put("vingt", 20) + .put("vingt deux", 22) + .put("vingt-deux", 22) + .put("vingt trois", 23) + .put("vingt-trois", 23) + .put("vingt quatre", 24) + .put("vingt-quatre", 24) + .put("vingt cinq", 25) + .put("vingt-cinq", 25) + .put("vingt six", 26) + .put("vingt-six", 26) + .put("vingt sept", 27) + .put("vingt-sept", 27) + .put("vingt huit", 28) + .put("vingt-huit", 28) + .put("vingt neuf", 29) + .put("vingt-neuf", 29) + .put("trente", 30) + .put("trente et un", 31) + .put("trente-et-un", 31) + .put("trente deux", 32) + .put("trente-deux", 32) + .put("trente trois", 33) + .put("trente-trois", 33) + .put("trente quatre", 34) + .put("trente-quatre", 34) + .put("trente cinq", 35) + .put("trente-cinq", 35) + .put("trente six", 36) + .put("trente-six", 36) + .put("trente sept", 37) + .put("trente-sept", 37) + .put("trente huit", 38) + .put("trente-huit", 38) + .put("trente neuf", 39) + .put("trente-neuf", 39) + .put("quarante", 40) + .put("quarante et un", 41) + .put("quarante-et-un", 41) + .put("quarante deux", 42) + .put("quarante-duex", 42) + .put("quarante trois", 43) + .put("quarante-trois", 43) + .put("quarante quatre", 44) + .put("quarante-quatre", 44) + .put("quarante cinq", 45) + .put("quarante-cinq", 45) + .put("quarante six", 46) + .put("quarante-six", 46) + .put("quarante sept", 47) + .put("quarante-sept", 47) + .put("quarante huit", 48) + .put("quarante-huit", 48) + .put("quarante neuf", 49) + .put("quarante-neuf", 49) + .put("cinquante", 50) + .put("cinquante et un", 51) + .put("cinquante-et-un", 51) + .put("cinquante deux", 52) + .put("cinquante-deux", 52) + .put("cinquante trois", 53) + .put("cinquante-trois", 53) + .put("cinquante quatre", 54) + .put("cinquante-quatre", 54) + .put("cinquante cinq", 55) + .put("cinquante-cinq", 55) + .put("cinquante six", 56) + .put("cinquante-six", 56) + .put("cinquante sept", 57) + .put("cinquante-sept", 57) + .put("cinquante huit", 58) + .put("cinquante-huit", 58) + .put("cinquante neuf", 59) + .put("cinquante-neuf", 59) + .put("soixante", 60) + .put("soixante et un", 61) + .put("soixante-et-un", 61) + .put("soixante deux", 62) + .put("soixante-deux", 62) + .put("soixante trois", 63) + .put("soixante-trois", 63) + .put("soixante quatre", 64) + .put("soixante-quatre", 64) + .put("soixante cinq", 65) + .put("soixante-cinq", 65) + .put("soixante six", 66) + .put("soixante-six", 66) + .put("soixante sept", 67) + .put("soixante-sept", 67) + .put("soixante huit", 68) + .put("soixante-huit", 68) + .put("soixante neuf", 69) + .put("soixante-neuf", 69) + .put("soixante dix", 70) + .put("soixante-dix", 70) + .put("soixante et onze", 71) + .put("soixante-et-onze", 71) + .put("soixante douze", 72) + .put("soixante-douze", 72) + .put("soixante treize", 73) + .put("soixante-treize", 73) + .put("soixante quatorze", 74) + .put("soixante-quatorze", 74) + .put("soixante quinze", 75) + .put("soixante-quinze", 75) + .put("soixante seize", 76) + .put("soixante-seize", 76) + .put("soixante dix sept", 77) + .put("soixante-dix-sept", 77) + .put("soixante dix huit", 78) + .put("soixante-dix-huit", 78) + .put("soixante dix neuf", 79) + .put("soixante-dix-neuf", 79) + .put("quatre vingt", 80) + .put("quatre-vingt", 80) + .put("quatre vingt un", 81) + .put("quatre-vingt-un", 81) + .put("quatre vingt deux", 82) + .put("quatre-vingt-duex", 82) + .put("quatre vingt trois", 83) + .put("quatre-vingt-trois", 83) + .put("quatre vingt quatre", 84) + .put("quatre-vingt-quatre", 84) + .put("quatre vingt cinq", 85) + .put("quatre-vingt-cinq", 85) + .put("quatre vingt six", 86) + .put("quatre-vingt-six", 86) + .put("quatre vingt sept", 87) + .put("quatre-vingt-sept", 87) + .put("quatre vingt huit", 88) + .put("quatre-vingt-huit", 88) + .put("quatre vingt neuf", 89) + .put("quatre-vingt-neuf", 89) + .put("quatre vingt dix", 90) + .put("quatre-vingt-dix", 90) + .put("quatre vingt onze", 91) + .put("quatre-vingt-onze", 91) + .put("quatre vingt douze", 92) + .put("quatre-vingt-douze", 92) + .put("quatre vingt treize", 93) + .put("quatre-vingt-treize", 93) + .put("quatre vingt quatorze", 94) + .put("quatre-vingt-quatorze", 94) + .put("quatre vingt quinze", 95) + .put("quatre-vingt-quinze", 95) + .put("quatre vingt seize", 96) + .put("quatre-vingt-seize", 96) + .put("quatre vingt dix sept", 97) + .put("quatre-vingt-dix-sept", 97) + .put("quatre vingt dix huit", 98) + .put("quatre-vingt-dix-huit", 98) + .put("quatre vingt dix neuf", 99) + .put("quatre-vingt-dix-neuf", 99) + .put("cent", 100) + .build(); + + public static final ImmutableMap DayOfMonth = ImmutableMap.builder() + .put("1er", 1) + .put("2e", 2) + .put("3e", 3) + .put("4e", 4) + .put("5e", 5) + .put("6e", 6) + .put("7e", 7) + .put("8e", 8) + .put("9e", 9) + .put("10e", 10) + .put("11e", 11) + .put("12e", 12) + .put("13e", 13) + .put("14e", 14) + .put("15e", 15) + .put("16e", 16) + .put("17e", 17) + .put("18e", 18) + .put("19e", 19) + .put("20e", 20) + .put("21e", 21) + .put("22e", 22) + .put("23e", 23) + .put("24e", 24) + .put("25e", 25) + .put("26e", 26) + .put("27e", 27) + .put("28e", 28) + .put("29e", 29) + .put("30e", 30) + .put("31e", 31) + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("demi", 0.5D) + .put("quart", 0.25D) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("fathers", new String[]{"peres", "pères", "fêtedespères", "fetedesperes"}) + .put("mothers", new String[]{"fêtedesmères", "fetedesmeres"}) + .put("thanksgiving", new String[]{"lactiondegrace", "lactiondegrâce", "jourdethanksgiving", "thanksgiving"}) + .put("martinlutherking", new String[]{"journeemartinlutherking", "martinlutherkingjr"}) + .put("washingtonsbirthday", new String[]{"washingtonsbirthday", "washingtonbirthday"}) + .put("canberra", new String[]{"canberraday"}) + .put("labour", new String[]{"fetedetravail", "travail", "fetedutravail"}) + .put("columbus", new String[]{"columbusday"}) + .put("memorial", new String[]{"jourcommémoratif", "jourcommemoratif"}) + .put("yuandan", new String[]{"yuandan", "nouvelanchinois"}) + .put("maosbirthday", new String[]{"maosbirthday"}) + .put("teachersday", new String[]{"teachersday", "teacherday"}) + .put("singleday", new String[]{"singleday"}) + .put("allsaintsday", new String[]{"allsaintsday"}) + .put("youthday", new String[]{"youthday"}) + .put("childrenday", new String[]{"childrenday", "childday"}) + .put("femaleday", new String[]{"femaleday"}) + .put("treeplantingday", new String[]{"treeplantingday"}) + .put("arborday", new String[]{"arborday"}) + .put("girlsday", new String[]{"girlsday"}) + .put("whiteloverday", new String[]{"whiteloverday"}) + .put("loverday", new String[]{"loverday"}) + .put("christmas", new String[]{"noel", "noël"}) + .put("xmas", new String[]{"xmas"}) + .put("newyear", new String[]{"nouvellesannees", "nouvelan"}) + .put("newyearday", new String[]{"jourdunouvelan"}) + .put("newyearsday", new String[]{"jourdel'an", "jourpremierdelannee", "jourpremierdelannée"}) + .put("inaugurationday", new String[]{"jourd'inaugueration", "inaugueration"}) + .put("groundhougday", new String[]{"marmotte"}) + .put("valentinesday", new String[]{"lasaint-valentin", "lasaintvalentin"}) + .put("stpatrickday", new String[]{"stpatrickday"}) + .put("aprilfools", new String[]{"poissond'avril"}) + .put("stgeorgeday", new String[]{"stgeorgeday"}) + .put("mayday", new String[]{"premier-mai", "ler-mai", "1-mai"}) + .put("cincodemayoday", new String[]{"cincodemayo"}) + .put("baptisteday", new String[]{"bapteme", "baptême"}) + .put("usindependenceday", new String[]{"l'independanceamericaine", "lindépendanceaméricaine"}) + .put("independenceday", new String[]{"l'indépendance", "lindependance"}) + .put("bastilleday", new String[]{"laprisedelabastille", "bastille"}) + .put("halloweenday", new String[]{"halloween"}) + .put("allhallowday", new String[]{"allhallowday"}) + .put("allsoulsday", new String[]{"allsoulsday"}) + .put("guyfawkesday", new String[]{"guyfawkesday"}) + .put("veteransday", new String[]{"veteransday"}) + .put("christmaseve", new String[]{"reveillondenoel", "réveillondenoël", "veilledenoel", "veilledenoël"}) + .put("newyeareve", new String[]{"réveillondenouvelan", "reveillondenouvelan", "lasaint-sylvestre", "lasaintsylvestre"}) + .build(); + + public static final String NightRegex = "\\b(minuit|nuit)\\b"; + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final String DefaultLanguageFallback = "DMY"; + + public static final List DurationDateRestrictions = Arrays.asList(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^([eé]t[eé])$", "(? AmbiguityTimeFiltersDict = ImmutableMap.builder() + .put("heures?$", "\\b(pour|durée\\s+de|pendant)\\s+(\\S+\\s+){1,2}heures?\\b") + .build(); + + public static final List MorningTermList = Arrays.asList("matinee", "matin", "matinée"); + + public static final List AfternoonTermList = Arrays.asList("apres-midi", "apres midi", "après midi", "après-midi"); + + public static final List EveningTermList = Arrays.asList("soir", "soiree", "soirée"); + + public static final List DaytimeTermList = Arrays.asList("jour", "journee", "journée"); + + public static final List NightTermList = Arrays.asList("nuit"); + + public static final List SameDayTerms = Arrays.asList("aujourd'hui", "auj"); + + public static final List PlusOneDayTerms = Arrays.asList("demain", "a2m1", "lendemain", "jour suivant"); + + public static final List MinusOneDayTerms = Arrays.asList("hier", "dernier"); + + public static final List PlusTwoDayTerms = Arrays.asList("après demain", "après-demain", "apres-demain"); + + public static final List MinusTwoDayTerms = Arrays.asList("avant-hier", "avant hier"); + + public static final List FutureStartTerms = Arrays.asList("cette"); + + public static final List FutureEndTerms = Arrays.asList("prochaine", "prochain"); + + public static final List LastCardinalTerms = Arrays.asList("dernières", "dernière", "dernieres", "derniere", "dernier"); + + public static final List MonthTerms = Arrays.asList("mois"); + + public static final List MonthToDateTerms = Arrays.asList("mois à ce jour"); + + public static final List WeekendTerms = Arrays.asList("fin de semaine", "le weekend"); + + public static final List WeekTerms = Arrays.asList("semaine"); + + public static final List YearTerms = Arrays.asList("années", "ans", "an", "l'annees", "l'annee"); + + public static final List YearToDateTerms = Arrays.asList("année à ce jour", "an à ce jour"); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java new file mode 100644 index 000000000..111190643 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java @@ -0,0 +1,983 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class PortugueseDateTime { + + public static final String LangMarker = "Por"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?\\b(at[eé]h?|[aà]s|ao?)\\b|--|-|—|——)(\\s+\\b(o|[aà](s)?)\\b)?"; + + public static final String RangeConnectorRegex = "(?(e\\s*(([àa]s?)|o)?)|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String DayRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|1|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)(?=\\b|t)"; + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)\\b"; + + public static final String AmDescRegex = "({BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "({BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "({BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(?({AmDescRegex}|{PmDescRegex}))" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String OfPrepositionRegex = "(\\bd(o|a|e)s?\\b)"; + + public static final String AfterNextSuffixRegex = "\\b(que\\s+vem|passad[oa])\\b"; + + public static final String RangePrefixRegex = "((de(sde)?|das?|entre)\\s+(a(s)?\\s+)?)"; + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d)))\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String RelativeRegex = "(?((est[ae]|pr[oó]xim[oa]|([uú]ltim(o|as|os)))(\\s+fina(l|is)\\s+d[eao])?)|(fina(l|is)\\s+d[eao]))\\b"; + + public static final String StrictRelativeRegex = "(?((est[ae]|pr[oó]xim[oa]|([uú]ltim(o|as|os)))(\\s+fina(l|is)\\s+d[eao])?)|(fina(l|is)\\s+d[eao]))\\b"; + + public static final String WrittenOneToNineRegex = "(uma?|dois|duas|tr[eê]s|quatro|cinco|seis|sete|oito|nove)"; + + public static final String WrittenOneHundredToNineHundredRegex = "(duzent[oa]s|trezent[oa]s|[cq]uatrocent[ao]s|quinhent[ao]s|seiscent[ao]s|setecent[ao]s|oitocent[ao]s|novecent[ao]s|cem|(?((dois\\s+)?mil)((\\s+e)?\\s+{WrittenOneHundredToNineHundredRegex})?((\\s+e)?\\s+{WrittenOneToNinetyNineRegex})?)" + .replace("{WrittenOneToNinetyNineRegex}", WrittenOneToNinetyNineRegex) + .replace("{WrittenOneHundredToNineHundredRegex}", WrittenOneHundredToNineHundredRegex); + + public static final String YearRegex = "({BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String RelativeMonthRegex = "(?([nd]?es[st]e|pr[óo]ximo|passsado|[uú]ltimo)\\s+m[eê]s)\\b"; + + public static final String MonthRegex = "(?abr(il)?|ago(sto)?|dez(embro)?|fev(ereiro)?|jan(eiro)?|ju[ln](ho)?|mar([çc]o)?|maio?|nov(embro)?|out(ubro)?|sep?t(embro)?)"; + + public static final String MonthSuffixRegex = "(?((em|no)\\s+|d[eo]\\s+)?({RelativeMonthRegex}|{MonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String DateUnitRegex = "(?anos?|meses|m[êe]s|semanas?|dias?)\\b"; + + public static final String PastRegex = "(?\\b(passad[ao](s)?|[uú]ltim[oa](s)?|anterior(es)?|h[aá]|pr[ée]vi[oa](s)?)\\b)"; + + public static final String FutureRegex = "(?\\b(seguinte(s)?|pr[oó]xim[oa](s)?|dentro\\s+de|em|daqui\\s+a)\\b)"; + + public static final String SimpleCasesRegex = "\\b((desde\\s+[oa]|desde|d[oa])\\s+)?(dia\\s+)?({DayRegex})\\s*{TillRegex}\\s*(o dia\\s+)?({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b{MonthSuffixRegex}\\s+((desde\\s+[oa]|desde|d[oa])\\s+)?(dia\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+((entre|entre\\s+[oa]s?)\\s+)(dias?\\s+)?({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String DayBetweenRegex = "\\b((entre|entre\\s+[oa]s?)\\s+)(dia\\s+)?({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String OneWordPeriodRegex = "\\b(((pr[oó]xim[oa]?|[nd]?es[st]e|aquel[ea]|[uú]ltim[oa]?|em)\\s+)?(?abr(il)?|ago(sto)?|dez(embro)?|fev(ereiro)?|jan(eiro)?|ju[ln](ho)?|mar([çc]o)?|maio?|nov(embro)?|out(ubro)?|sep?t(embro)?)|(?<=\\b(de|do|da|o|a)\\s+)?(pr[oó]xim[oa](s)?|[uú]ltim[oa]s?|est(e|a))\\s+(fim de semana|fins de semana|semana|m[êe]s|ano)|fim de semana|fins de semana|(m[êe]s|anos)? [àa] data)\\b"; + + public static final String MonthWithYearRegex = "\\b(((pr[oó]xim[oa](s)?|[nd]?es[st]e|aquele|[uú]ltim[oa]?|em)\\s+)?(?abr(il)?|ago(sto)?|dez(embro)?|fev(ereiro)?|jan(eiro)?|ju[ln](ho)?|mar([çc]o)?|maio?|nov(embro)?|out(ubro)?|sep?t(embro)?)\\s+((de|do|da|o|a)\\s+)?({YearRegex}|{TwoDigitYearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|[nd]?es[st]e)\\s+ano))\\b" + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex); + + public static final String MonthNumWithYearRegex = "({YearRegex}(\\s*?)[/\\-\\.](\\s*?){MonthNumRegex})|({MonthNumRegex}(\\s*?)[/\\-](\\s*?){YearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "(?(a|na\\s+)?(?primeira?|1a|segunda|2a|terceira|3a|[qc]uarta|4a|quinta|5a|[uú]ltima)\\s+semana\\s+{MonthSuffixRegex})" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String WeekOfYearRegex = "(?(a|na\\s+)?(?primeira?|1a|segunda|2a|terceira|3a|[qc]uarta|4a|quinta|5a|[uú]ltima?)\\s+semana(\\s+d[oe]?)?\\s+({YearRegex}|(?pr[oó]ximo|[uú]ltimo|[nd]?es[st]e)\\s+ano))" + .replace("{YearRegex}", YearRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterRegex = "(n?o\\s+)?(?primeiro|1[oº]|segundo|2[oº]|terceiro|3[oº]|[qc]uarto|4[oº])\\s+trimestre(\\s+d[oe]|\\s*,\\s*)?\\s+({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|[nd]?es[st]e)\\s+ano)" + .replace("{YearRegex}", YearRegex); + + public static final String QuarterRegexYearFront = "({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|[nd]?es[st]e)\\s+ano)\\s+(n?o\\s+)?(?(primeiro)|1[oº]|segundo|2[oº]|terceiro|3[oº]|[qc]uarto|4[oº])\\s+trimestre" + .replace("{YearRegex}", YearRegex); + + public static final String AllHalfYearRegex = "^[.]"; + + public static final String PrefixDayRegex = "^[.]"; + + public static final String SeasonRegex = "\\b(?(([uú]ltim[oa]|[nd]?es[st][ea]|n?[oa]|(pr[oó]xim[oa]s?|seguinte))\\s+)?(?primavera|ver[ãa]o|outono|inverno)((\\s+)?(seguinte|((de\\s+|,)?\\s*{YearRegex})|((do\\s+)?(?pr[oó]ximo|[uú]ltimo|[nd]?es[st]e)\\s+ano)))?)\\b" + .replace("{YearRegex}", YearRegex); + + public static final String WhichWeekRegex = "\\b(semana)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "(semana)(\\s*)((do|da|de))"; + + public static final String MonthOfRegex = "(mes)(\\s*)((do|da|de))"; + + public static final String RangeUnitRegex = "\\b(?anos?|meses|m[êe]s|semanas?)\\b"; + + public static final String BeforeAfterRegex = "^[.]"; + + public static final String InConnectorRegex = "\\b(em)\\b"; + + public static final String SinceYearSuffixRegex = "^[.]"; + + public static final String WithinNextPrefixRegex = "^[.]"; + + public static final String TodayNowRegex = "\\b(hoje|agora)\\b"; + + public static final String CenturySuffixRegex = "^[.]"; + + public static final String FromRegex = "((desde|de)(\\s*a(s)?)?)$"; + + public static final String BetweenRegex = "(entre\\s*([oa](s)?)?)"; + + public static final String WeekDayRegex = "\\b(?(domingos?|(segunda|ter[çc]a|quarta|quinta|sexta)s?([-\\s+]feiras?)?|s[aá]bados?|(2|3|4|5|6)[aª])\\b|(dom|seg|ter[cç]|qua|qui|sex|sab)\\b(\\.?(?=\\s|,|;|$)))"; + + public static final String OnRegex = "(?<=\\b(em|no)\\s+)({DayRegex}s?)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String RelaxedOnRegex = "(?<=\\b(em|n[oa]|d[oa])\\s+)(dia\\s+)?((?10|11|12|13|14|15|16|17|18|19|1|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)s?)\\b"; + + public static final String ThisRegex = "\\b(([nd]?es[st][ea]\\s*){WeekDayRegex})|({WeekDayRegex}\\s*([nd]?es[st]a\\s+semana))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String LastDateRegex = "\\b(([uú]ltim[ao])\\s*{WeekDayRegex})|({WeekDayRegex}(\\s+(([nd]?es[st]a|na|da)\\s+([uú]ltima\\s+)?semana)))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String NextDateRegex = "\\b(((pr[oó]xim[oa]|seguinte)\\s*){WeekDayRegex})|({WeekDayRegex}((\\s+(pr[oó]xim[oa]|seguinte))|(\\s+(da\\s+)?(semana\\s+seguinte|pr[oó]xima\\s+semana))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDayRegex = "\\b((d?o\\s+)?(dia\\s+antes\\s+de\\s+ontem|antes\\s+de\\s+ontem|anteontem)|((d?o\\s+)?(dia\\s+|depois\\s+|dia\\s+depois\\s+)?de\\s+amanh[aã])|(o\\s)?dia\\s+seguinte|(o\\s)?pr[oó]ximo\\s+dia|(o\\s+)?[uú]ltimo\\s+dia|ontem|amanh[ãa]|hoje)|(do\\s+dia$)\\b"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String ForTheRegex = ".^"; + + public static final String WeekDayAndDayOfMonthRegex = ".^"; + + public static final String WeekDayAndDayRegex = ".^"; + + public static final String WeekDayOfMonthRegex = "(?(n?[ao]\\s+)?(?primeir[ao]|1[ao]|segund[ao]|2[ao]|terceir[ao]|3[ao]|[qc]uart[ao]|4[ao]|quint[ao]|5[ao]|[uú]ltim[ao])\\s+{WeekDayRegex}\\s+{MonthSuffixRegex})" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "^[.]"; + + public static final String AmbiguousRangeModifierPrefix = "^[.]"; + + public static final String NumberEndingPattern = "^[.]"; + + public static final String SpecialDateRegex = "(?<=\\bno\\s+){DayRegex}\\b" + .replace("{DayRegex}", DayRegex); + + public static final String OfMonthRegex = "^\\s*de\\s*{MonthSuffixRegex}" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String MonthEndRegex = "({MonthRegex}\\s*(o)?\\s*$)" + .replace("{MonthRegex}", MonthRegex); + + public static final String WeekDayEnd = "{WeekDayRegex}\\s*,?\\s*$" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayStart = "^[\\.]"; + + public static final String DateYearRegex = "(?{YearRegex}|{TwoDigitYearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex); + + public static final String DateExtractor1 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}?((\\s*(de)|[/\\\\\\.\\-])\\s*)?{MonthRegex}\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String DateExtractor2 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}\\s*([\\.\\-]|de)?\\s*{MonthRegex}(\\s*(,|de)\\s*){DateYearRegex}\\b" + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateExtractor3 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}(\\s+|\\s*,\\s*|\\s+de\\s+|\\s*-\\s*){MonthRegex}((\\s+|\\s*(,|de)\\s*){DateYearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor4 = "\\b{MonthNumRegex}\\s*[/\\\\\\-]\\s*{DayRegex}\\s*[/\\\\\\-]\\s*{DateYearRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor5 = "\\b{DayRegex}\\s*[/\\\\\\-\\.]\\s*({MonthNumRegex}|{MonthRegex})\\s*[/\\\\\\-\\.]\\s*{DateYearRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor6 = "(?<=\\b(em|no|o)\\s+){MonthNumRegex}[\\-\\.]{DayRegex}\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor7 = "\\b{MonthNumRegex}\\s*/\\s*{DayRegex}((\\s+|\\s*(,|de)\\s*){DateYearRegex})?\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor8 = "(?<=\\b(em|no|o)\\s+){DayRegex}[\\\\\\-]{MonthNumRegex}\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor9 = "\\b{DayRegex}\\s*/\\s*{MonthNumRegex}((\\s+|\\s*(,|de)\\s*){DateYearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor10 = "\\b{YearRegex}\\s*[/\\\\\\-\\.]\\s*{MonthNumRegex}\\s*[/\\\\\\-\\.]\\s*{DayRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor11 = "(?<=\\b(dia)\\s+){DayRegex}" + .replace("{DayRegex}", DayRegex); + + public static final String HourNumRegex = "\\b(?zero|uma|duas|tr[êe]s|[qc]uatro|cinco|seis|sete|oito|nove|dez|onze|doze)\\b"; + + public static final String MinuteNumRegex = "(?um|dois|tr[êe]s|[qc]uatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|catorze|quatorze|quinze|dez[ea]sseis|dez[ea]sete|dezoito|dez[ea]nove|vinte|trinta|[qc]uarenta|cin[qc]uenta)"; + + public static final String DeltaMinuteNumRegex = "(?um|dois|tr[êe]s|[qc]uatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|catorze|quatorze|quinze|dez[ea]sseis|dez[ea]sete|dezoito|dez[ea]nove|vinte|trinta|[qc]uarenta|cin[qc]uenta)"; + + public static final String OclockRegex = "(?em\\s+ponto)"; + + public static final String PmRegex = "(?((pela|de|da|\\b[àa]\\b|na)\\s+(tarde|noite)))|((depois\\s+do|ap[óo]s\\s+o)\\s+(almo[çc]o|meio dia|meio-dia))"; + + public static final String AmRegex = "(?(pela|de|da|na)\\s+(manh[ãa]|madrugada))"; + + public static final String AmTimeRegex = "(?([dn]?es[st]a|(pela|de|da|na))\\s+(manh[ãa]|madrugada))"; + + public static final String PmTimeRegex = "(?(([dn]?es[st]a|\\b[àa]\\b|(pela|de|da|na))\\s+(tarde|noite)))|((depois\\s+do|ap[óo]s\\s+o)\\s+(almo[çc]o|meio dia|meio-dia))"; + + public static final String LessThanOneHour = "(?((\\s+e\\s+)?(quinze|(um\\s+|dois\\s+|tr[êes]\\s+)?quartos?)|quinze|(\\s*)(um\\s+|dois\\s+|tr[êes]\\s+)?quartos?|(\\s+e\\s+)(meia|trinta)|{BaseDateTime.DeltaMinuteRegex}(\\s+(minuto|minutos|min|mins))|{DeltaMinuteNumRegex}(\\s+(minuto|minutos|min|mins))))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String TensTimeRegex = "(?dez|vinte|trinta|[qc]uarenta|cin[qc]uenta)"; + + public static final String WrittenTimeRegex = "(?({HourNumRegex}\\s*((e|menos)\\s+)?({MinuteNumRegex}|({TensTimeRegex}((\\s*e\\s+)?{MinuteNumRegex}))))|(({MinuteNumRegex}|({TensTimeRegex}((\\s*e\\s+)?{MinuteNumRegex})?))\\s*((para as|pras|antes da|antes das)\\s+)?({HourNumRegex}|{BaseDateTime.HourRegex})))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String TimePrefix = "(?{LessThanOneHour}(\\s+(passad[ao]s)\\s+(as)?|\\s+depois\\s+(das?|do)|\\s+pras?|\\s+(para|antes)?\\s+([àa]s?))?)" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?({LessThanOneHour}\\s+)?({AmRegex}|{PmRegex}|{OclockRegex}))" + .replace("{LessThanOneHour}", LessThanOneHour) + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex})" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String AtRegex = "\\b((?<=\\b([aà]s?)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})(\\s+horas?|\\s*h\\b)?|(?<=\\b(s(er)?[aã]o|v[aã]o\\s+ser|^[eé]h?)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})(\\s+horas?|\\s*h\\b))(\\s+{OclockRegex})?\\b" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String ConnectNumRegex = "({BaseDateTime.HourRegex}(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)\\s*{DescRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex1 = "(\\b{TimePrefix}\\s+)?({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\s*({DescRegex})" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex}(\\s*{DescRegex})" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex4 = "\\b(({DescRegex}?)|({BasicTime}?)({DescRegex}?))({TimePrefix}\\s*)({HourNumRegex}|{BaseDateTime.HourRegex})?(\\s+{TensTimeRegex}(\\s+e\\s+)?{MinuteNumRegex}?)?({OclockRegex})?\\b" + .replace("{DescRegex}", DescRegex) + .replace("{BasicTime}", BasicTime) + .replace("{TimePrefix}", TimePrefix) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String TimeRegex5 = "\\b({TimePrefix}|{BasicTime}{TimePrefix})\\s+(\\s*{DescRegex})?{BasicTime}?\\s*{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex6 = "({BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b)" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffix}\\s+[àa]s?\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = "\\b{TimeSuffix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b(?{HourNumRegex}\\s+({TensTimeRegex}\\s*)(e\\s+)?{MinuteNumRegex}?)\\b" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimeRegex10 = "(\\b([àa]|ao?)|na|de|da|pela)\\s+(madrugada|manh[ãa]|meio\\s*dia|meia\\s*noite|tarde|noite)"; + + public static final String TimeRegex11 = "\\b({WrittenTimeRegex})(\\s+{DescRegex})?\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex12 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String PrepositionRegex = "(?([àa]s?|em|por|pelo|pela|no|na|de|d[oa]?)?$)"; + + public static final String NowRegex = "\\b(?((logo|exatamente)\\s+)?agora(\\s+mesmo)?|neste\\s+momento|(assim\\s+que|t[ãa]o\\s+cedo\\s+quanto)\\s+(poss[ií]vel|possas?|possamos)|o\\s+mais\\s+(cedo|r[aá]pido)\\s+poss[íi]vel|recentemente|previamente)\\b"; + + public static final String SuffixRegex = "^\\s*((e|a|em|por|pelo|pela|no|na|de)\\s+)?(manh[ãa]|madrugada|meio\\s*dia|tarde|noite)\\b"; + + public static final String TimeOfDayRegex = "\\b(?manh[ãa]|madrugada|tarde|noite|((depois\\s+do|ap[óo]s\\s+o)\\s+(almo[çc]o|meio dia|meio-dia)))\\b"; + + public static final String SpecificTimeOfDayRegex = "\\b(((((a)?\\s+|[nd]?es[st]a|seguinte|pr[oó]xim[oa]|[uú]ltim[oa])\\s+)?{TimeOfDayRegex}))\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String TimeOfTodayAfterRegex = "^\\s*(,\\s*)?([àa]|em|por|pelo|pela|de|no|na?\\s+)?{SpecificTimeOfDayRegex}" + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex); + + public static final String TimeOfTodayBeforeRegex = "({SpecificTimeOfDayRegex}(\\s*,)?(\\s+(a\\s+la(s)?|para))?\\s*)" + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayAfterRegex = "({HourNumRegex}|{BaseDateTime.HourRegex})\\s*(,\\s*)?((en|de(l)?)?\\s+)?{SpecificTimeOfDayRegex}" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayBeforeRegex = "({SpecificTimeOfDayRegex}(\\s*,)?(\\s+(a\\s+la|para))?\\s*({HourNumRegex}|{BaseDateTime.HourRegex}))" + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String SpecificEndOfRegex = "((no|ao)\\s+)?(fi(m|nal)|t[ée]rmin(o|ar))(\\s+d?o(\\s+dia)?(\\s+de)?)?\\s*$"; + + public static final String UnspecificEndOfRegex = "^[.]"; + + public static final String UnspecificEndOfRangeRegex = "^[.]"; + + public static final String UnitRegex = "(?anos|ano|meses|m[êe]s|semanas|semana|dias|dia|horas|hora|h|hr|hrs|hs|minutos|minuto|mins|min|segundos|segundo|segs|seg)\\b"; + + public static final String ConnectorRegex = "^(,|t|para [ao]|para as|pras|cerca de|cerca das|perto de|perto das|quase)$"; + + public static final String TimeHourNumRegex = "(?vinte e um|vinte e dois|vinte e tr[êe]s|vinte e quatro|zero|um|uma|dois|duas|tr[êe]s|quatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|quatorze|catorze|quinze|dez[ea]sseis|dez[ea]ssete|dezoito|dez[ea]nove|vinte)"; + + public static final String PureNumFromTo = "((desde|de|da|das)\\s+(a(s)?\\s+)?)?({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?\\s*{TillRegex}\\s*({BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String PureNumBetweenAnd = "(entre\\s+((a|as)?\\s+)?)({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?\\s*e\\s*(a(s)?\\s+)?({BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String SpecificTimeFromTo = "^[.]"; + + public static final String SpecificTimeBetweenAnd = "^[.]"; + + public static final String TimeUnitRegex = "(?horas|hora|h|minutos|minuto|mins|min|segundos|segundo|secs|sec)\\b"; + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String DateTimePeriodNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String PeriodTimeOfDayWithDateRegex = "\\b((e|[àa]|em|na|no|ao|pel[ao]|de)\\s+)?(?manh[ãa]|madrugada|(passado\\s+(o\\s+)?)?meio\\s+dia|tarde|noite)\\b"; + + public static final String RelativeTimeUnitRegex = "({PastRegex}|{FutureRegex})\\s+{UnitRegex}|{UnitRegex}\\s+({PastRegex}|{FutureRegex})" + .replace("{PastRegex}", PastRegex) + .replace("{FutureRegex}", FutureRegex) + .replace("{UnitRegex}", UnitRegex); + + public static final String SuffixAndRegex = "(?\\s*(e)\\s+(?meia|(um\\s+)?quarto))"; + + public static final String FollowedUnit = "^\\s*{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String LessThanRegex = "^[.]"; + + public static final String MoreThanRegex = "^[.]"; + + public static final String DurationNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?){UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String AnUnitRegex = "\\b(um(a)?)\\s+{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DuringRegex = "^[.]"; + + public static final String AllRegex = "\\b(?tod[oa]?\\s+(o|a)\\s+(?ano|m[êe]s|semana|dia))\\b"; + + public static final String HalfRegex = "\\b(?mei[oa]\\s+(?ano|m[êe]s|semana|dia|hora))\\b"; + + public static final String ConjunctionRegex = "^[.]"; + + public static final String InexactNumberRegex = "\\b(poucos|pouco|algum|alguns|v[áa]rios)\\b"; + + public static final String InexactNumberUnitRegex = "\\b(poucos|pouco|algum|alguns|v[áa]rios)\\s+{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String HolidayRegex1 = "\\b(?sexta-feira santa|sexta-feira da paix[ãa]o|quarta-feira de cinzas|carnaval|dia (de|de los) presidentes?|ano novo chin[eê]s|ano novo|v[ée]spera de ano novo|natal|v[ée]spera de natal|dia de a[cç][ãa]o de gra[çc]as|a[cç][ãa]o de gra[çc]as|yuandan|halloween|dia das bruxas|p[áa]scoa)(\\s+(d[eo]?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|[nd]?es[st][ea]|[uú]ltim[oa]?|em))\\s+ano))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex2 = "\\b(?(dia\\s+(d[eoa]s?\\s+)?)?(martin luther king|todos os santos|s[ãa]o (patr[íi]cio|francisco|jorge|jo[ãa]o)|independ[êe]ncia))(\\s+(d[eo]?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|[nd]?es[st][ea]|[uú]ltim[oa]?|em))\\s+ano))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex3 = "\\b(?(dia\\s+d[eoa]s?\\s+)(trabalh(o|ador(es)?)|m[ãa]es?|pais?|mulher(es)?|crian[çc]as?|marmota|professor(es)?))(\\s+(d[eo]?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|[nd]?es[st][ea]|[uú]ltim[oa]?|em))\\s+ano))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String BeforeRegex = "(antes(\\s+(de|dos?|das?)?)?)"; + + public static final String AfterRegex = "((depois|ap[óo]s)(\\s*(de|d?os?|d?as?)?)?)"; + + public static final String SinceRegex = "(desde(\\s+(as?|o))?)"; + + public static final String AroundRegex = "^[.]"; + + public static final String PeriodicRegex = "\\b(?di[áa]ri[ao]|diariamente|mensalmente|semanalmente|quinzenalmente|anualmente)\\b"; + + public static final String EachExpression = "cada|tod[oa]s?\\s*([oa]s)?"; + + public static final String EachUnitRegex = "(?({EachExpression})\\s*{UnitRegex})" + .replace("{EachExpression}", EachExpression) + .replace("{UnitRegex}", UnitRegex); + + public static final String EachPrefixRegex = "(?({EachExpression})\\s*$)" + .replace("{EachExpression}", EachExpression); + + public static final String EachDayRegex = "\\s*({EachExpression})\\s*dias\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String BeforeEachDayRegex = "({EachExpression})\\s*dias(\\s+(as|ao))?\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String SetEachRegex = "(?({EachExpression})\\s*)" + .replace("{EachExpression}", EachExpression); + + public static final String LaterEarlyPeriodRegex = "^[.]"; + + public static final String WeekWithWeekDayRangeRegex = "^[.]"; + + public static final String GeneralEndingRegex = "^[.]"; + + public static final String MiddlePauseRegex = "^[.]"; + + public static final String PrefixArticleRegex = "^[\\.]"; + + public static final String OrRegex = "^[.]"; + + public static final String YearPlusNumberRegex = "^[.]"; + + public static final String NumberAsTimeRegex = "^[.]"; + + public static final String TimeBeforeAfterRegex = "^[.]"; + + public static final String DateNumberConnectorRegex = "^[.]"; + + public static final String ComplexDatePeriodRegex = "^[.]"; + + public static final String AgoRegex = "\\b(antes|atr[áa]s|no passado)\\b"; + + public static final String LaterRegex = "\\b(depois d[eoa]s?|ap[óo]s (as)?|desde (as|o)|desde|no futuro|mais tarde)\\b"; + + public static final String Tomorrow = "amanh[ãa]"; + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("anos", "Y") + .put("ano", "Y") + .put("meses", "MON") + .put("mes", "MON") + .put("mês", "MON") + .put("semanas", "W") + .put("semana", "W") + .put("dias", "D") + .put("dia", "D") + .put("horas", "H") + .put("hora", "H") + .put("hrs", "H") + .put("hr", "H") + .put("h", "H") + .put("minutos", "M") + .put("minuto", "M") + .put("mins", "M") + .put("min", "M") + .put("segundos", "S") + .put("segundo", "S") + .put("segs", "S") + .put("seg", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("anos", 31536000L) + .put("ano", 31536000L) + .put("meses", 2592000L) + .put("mes", 2592000L) + .put("mês", 2592000L) + .put("semanas", 604800L) + .put("semana", 604800L) + .put("dias", 86400L) + .put("dia", 86400L) + .put("horas", 3600L) + .put("hora", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutos", 60L) + .put("minuto", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("segundos", 1L) + .put("segundo", 1L) + .put("segs", 1L) + .put("seg", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("primavera", "SP") + .put("verao", "SU") + .put("verão", "SU") + .put("outono", "FA") + .put("inverno", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("primeiro", 1) + .put("primeira", 1) + .put("1o", 1) + .put("1a", 1) + .put("segundo", 2) + .put("segunda", 2) + .put("2o", 2) + .put("2a", 2) + .put("terceiro", 3) + .put("terceira", 3) + .put("3o", 3) + .put("3a", 3) + .put("cuarto", 4) + .put("quarto", 4) + .put("cuarta", 4) + .put("quarta", 4) + .put("4o", 4) + .put("4a", 4) + .put("quinto", 5) + .put("quinta", 5) + .put("5o", 5) + .put("5a", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("segunda-feira", 1) + .put("segundas-feiras", 1) + .put("segunda feira", 1) + .put("segundas feiras", 1) + .put("segunda", 1) + .put("segundas", 1) + .put("terça-feira", 2) + .put("terças-feiras", 2) + .put("terça feira", 2) + .put("terças feiras", 2) + .put("terça", 2) + .put("terças", 2) + .put("terca-feira", 2) + .put("tercas-feiras", 2) + .put("terca feira", 2) + .put("tercas feiras", 2) + .put("terca", 2) + .put("tercas", 2) + .put("quarta-feira", 3) + .put("quartas-feiras", 3) + .put("quarta feira", 3) + .put("quartas feiras", 3) + .put("quarta", 3) + .put("quartas", 3) + .put("quinta-feira", 4) + .put("quintas-feiras", 4) + .put("quinta feira", 4) + .put("quintas feiras", 4) + .put("quinta", 4) + .put("quintas", 4) + .put("sexta-feira", 5) + .put("sextas-feiras", 5) + .put("sexta feira", 5) + .put("sextas feiras", 5) + .put("sexta", 5) + .put("sextas", 5) + .put("sabado", 6) + .put("sabados", 6) + .put("sábado", 6) + .put("sábados", 6) + .put("domingo", 0) + .put("domingos", 0) + .put("seg", 1) + .put("seg.", 1) + .put("2a", 1) + .put("ter", 2) + .put("ter.", 2) + .put("3a", 2) + .put("qua", 3) + .put("qua.", 3) + .put("4a", 3) + .put("qui", 4) + .put("qui.", 4) + .put("5a", 4) + .put("sex", 5) + .put("sex.", 5) + .put("6a", 5) + .put("sab", 6) + .put("sab.", 6) + .put("dom", 0) + .put("dom.", 0) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("janeiro", 1) + .put("fevereiro", 2) + .put("março", 3) + .put("marco", 3) + .put("abril", 4) + .put("maio", 5) + .put("junho", 6) + .put("julho", 7) + .put("agosto", 8) + .put("septembro", 9) + .put("setembro", 9) + .put("outubro", 10) + .put("novembro", 11) + .put("dezembro", 12) + .put("jan", 1) + .put("fev", 2) + .put("mar", 3) + .put("abr", 4) + .put("mai", 5) + .put("jun", 6) + .put("jul", 7) + .put("ago", 8) + .put("sept", 9) + .put("set", 9) + .put("out", 10) + .put("nov", 11) + .put("dez", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("zero", 0) + .put("um", 1) + .put("uma", 1) + .put("dois", 2) + .put("tres", 3) + .put("três", 3) + .put("quatro", 4) + .put("cinco", 5) + .put("seis", 6) + .put("sete", 7) + .put("oito", 8) + .put("nove", 9) + .put("dez", 10) + .put("onze", 11) + .put("doze", 12) + .put("dezena", 12) + .put("dezenas", 12) + .put("treze", 13) + .put("catorze", 14) + .put("quatorze", 14) + .put("quinze", 15) + .put("dezesseis", 16) + .put("dezasseis", 16) + .put("dezessete", 17) + .put("dezassete", 17) + .put("dezoito", 18) + .put("dezenove", 19) + .put("dezanove", 19) + .put("vinte", 20) + .put("vinte e um", 21) + .put("vinte e uma", 21) + .put("vinte e dois", 22) + .put("vinte e duas", 22) + .put("vinte e tres", 23) + .put("vinte e três", 23) + .put("vinte e quatro", 24) + .put("vinte e cinco", 25) + .put("vinte e seis", 26) + .put("vinte e sete", 27) + .put("vinte e oito", 28) + .put("vinte e nove", 29) + .put("trinta", 30) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("pai", new String[]{"diadopai", "diadospais"}) + .put("mae", new String[]{"diadamae", "diadasmaes"}) + .put("acaodegracas", new String[]{"diadegracas", "diadeacaodegracas", "acaodegracas"}) + .put("trabalho", new String[]{"diadotrabalho", "diadotrabalhador", "diadostrabalhadores"}) + .put("pascoa", new String[]{"diadepascoa", "pascoa"}) + .put("natal", new String[]{"natal", "diadenatal"}) + .put("vesperadenatal", new String[]{"vesperadenatal"}) + .put("anonovo", new String[]{"anonovo", "diadeanonovo", "diadoanonovo"}) + .put("vesperadeanonovo", new String[]{"vesperadeanonovo", "vesperadoanonovo"}) + .put("yuandan", new String[]{"yuandan"}) + .put("todosossantos", new String[]{"todosossantos"}) + .put("professor", new String[]{"diadoprofessor", "diadosprofessores"}) + .put("crianca", new String[]{"diadacrianca", "diadascriancas"}) + .put("mulher", new String[]{"diadamulher"}) + .build(); + + public static final ImmutableMap VariableHolidaysTimexDictionary = ImmutableMap.builder() + .put("pai", "-06-WXX-7-3") + .put("mae", "-05-WXX-7-2") + .put("acaodegracas", "-11-WXX-4-4") + .put("memoria", "-03-WXX-2-4") + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("metade", 0.5D) + .put("quarto", 0.25D) + .build(); + + public static final String DateTokenPrefix = "em "; + + public static final String TimeTokenPrefix = "as "; + + public static final String TokenBeforeDate = "o "; + + public static final String TokenBeforeTime = "as "; + + public static final String UpcomingPrefixRegex = ".^"; + + public static final String NextPrefixRegex = "(pr[oó]xim[oa]|seguinte|{UpcomingPrefixRegex})\\b" + .replace("{UpcomingPrefixRegex}", UpcomingPrefixRegex); + + public static final String PastPrefixRegex = ".^"; + + public static final String PreviousPrefixRegex = "([uú]ltim[oa]|{PastPrefixRegex})\\b" + .replace("{PastPrefixRegex}", PastPrefixRegex); + + public static final String ThisPrefixRegex = "([nd]?es[st][ea])\\b"; + + public static final String RelativeDayRegex = "^[\\.]"; + + public static final String RestOfDateRegex = "^[\\.]"; + + public static final String RelativeDurationUnitRegex = "^[\\.]"; + + public static final String ReferenceDatePeriodRegex = "^[.]"; + + public static final String FromToRegex = "\\b(from).+(to)\\b.+"; + + public static final String SingleAmbiguousMonthRegex = "^(the\\s+)?(may|march)$"; + + public static final String UnspecificDatePeriodRegex = "^[.]"; + + public static final String PrepositionSuffixRegex = "\\b(on|in|at|around|from|to)$"; + + public static final String RestOfDateTimeRegex = "^[\\.]"; + + public static final String SetWeekDayRegex = "^[\\.]"; + + public static final String NightRegex = "\\b(meia noite|noite|de noite)\\b"; + + public static final String CommonDatePrefixRegex = "\\b(dia)\\s+$"; + + public static final String DurationUnitRegex = "^[\\.]"; + + public static final String DurationConnectorRegex = "^[.]"; + + public static final String CenturyRegex = "^[.]"; + + public static final String DecadeRegex = "^[.]"; + + public static final String DecadeWithCenturyRegex = "^[.]"; + + public static final String RelativeDecadeRegex = "^[.]"; + + public static final String YearSuffix = "((,|\\sde)?\\s*({YearRegex}|{FullTextYearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String SuffixAfterRegex = "^[.]"; + + public static final String YearPeriodRegex = "^[.]"; + + public static final String FutureSuffixRegex = "^[.]"; + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final String DefaultLanguageFallback = "DMY"; + + public static final List DurationDateRestrictions = Arrays.asList(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); + + public static final List EarlyMorningTermList = Arrays.asList("madrugada"); + + public static final List MorningTermList = Arrays.asList("manha", "manhã"); + + public static final List AfternoonTermList = Arrays.asList("passado o meio dia", "depois do meio dia"); + + public static final List EveningTermList = Arrays.asList("tarde"); + + public static final List NightTermList = Arrays.asList("noite"); + + public static final List SameDayTerms = Arrays.asList("hoje", "este dia", "esse dia", "o dia"); + + public static final List PlusOneDayTerms = Arrays.asList("amanha", "de amanha", "dia seguinte", "o dia de amanha", "proximo dia"); + + public static final List MinusOneDayTerms = Arrays.asList("ontem", "ultimo dia"); + + public static final List PlusTwoDayTerms = Arrays.asList("depois de amanha", "dia depois de amanha"); + + public static final List MinusTwoDayTerms = Arrays.asList("anteontem", "dia antes de ontem"); + + public static final List MonthTerms = Arrays.asList("mes", "meses"); + + public static final List MonthToDateTerms = Arrays.asList("mes ate agora", "mes ate hoje", "mes ate a data"); + + public static final List WeekendTerms = Arrays.asList("fim de semana"); + + public static final List WeekTerms = Arrays.asList("semana"); + + public static final List YearTerms = Arrays.asList("ano", "anos"); + + public static final List YearToDateTerms = Arrays.asList("ano ate agora", "ano ate hoje", "ano ate a data", "anos ate agora", "anos ate hoje", "anos ate a data"); + + public static final ImmutableMap SpecialCharactersEquivalent = ImmutableMap.builder() + .put('á', 'a') + .put('é', 'e') + .put('í', 'i') + .put('ó', 'o') + .put('ú', 'u') + .put('ê', 'e') + .put('ô', 'o') + .put('ü', 'u') + .put('ã', 'a') + .put('õ', 'o') + .put('ç', 'c') + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java new file mode 100644 index 000000000..fb51b40ac --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java @@ -0,0 +1,1204 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class SpanishDateTime { + + public static final String LangMarker = "Spa"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?\\b(hasta|hacia|al?)\\b(\\s+(el|la(s)?)\\b)?|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String StrictTillRegex = "(?\\b(hasta|hacia|al?)(\\s+(el|la(s)?))?\\b|{BaseDateTime.RangeConnectorSymbolRegex}(?!\\s*[qt][1-4](?!(\\s+de|\\s*,\\s*))))" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String RangeConnectorRegex = "(?\\b(y\\s*(el|(la(s)?)?))\\b|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String WrittenDayRegex = "(?uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|dieciséis|diecisiete|dieciocho|diecinueve|veinte|veintiuno|veintidós|veintitrés|veinticuatro|veinticinco|veintiséis|veintisiete|veintiocho|veintinueve|treinta(\\s+y\\s+uno)?)"; + + public static final String DayRegex = "\\b(?01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|1|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)(?:\\.[º°])?(?=\\b|t)"; + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)\\b"; + + public static final String OclockRegex = "(?en\\s+punto)"; + + public static final String AmDescRegex = "({BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "({BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "({BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(?({AmDescRegex}|{PmDescRegex}))" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String OfPrepositionRegex = "(\\bd(o|al?|el?)\\b)"; + + public static final String AfterNextSuffixRegex = "\\b(despu[eé]s\\s+de\\s+la\\s+pr[oó]xima)\\b"; + + public static final String NextSuffixRegex = "\\b(que\\s+viene|pr[oó]xim[oa]|siguiente)\\b"; + + public static final String PreviousSuffixRegex = "\\b(pasad[ao]|anterior(?!\\s+(al?|del?)\\b))\\b"; + + public static final String RelativeSuffixRegex = "({AfterNextSuffixRegex}|{NextSuffixRegex}|{PreviousSuffixRegex})" + .replace("{AfterNextSuffixRegex}", AfterNextSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PreviousSuffixRegex}", PreviousSuffixRegex); + + public static final String RangePrefixRegex = "((de(l|sde)?|entre)(\\s+la(s)?)?)"; + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d))|\\.?[º°ª])\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String RelativeRegex = "(?est[ae]|pr[oó]xim[oa]|siguiente|(([uú]ltim|pasad)(o|as|os)))\\b"; + + public static final String StrictRelativeRegex = "(?est[ae]|pr[oó]xim[oa]|siguiente|(([uú]ltim|pasad)(o|as|os)))\\b"; + + public static final String WrittenOneToNineRegex = "(un[ao]?|dos|tres|cuatro|cinco|seis|siete|ocho|nueve)"; + + public static final String WrittenOneHundredToNineHundredRegex = "(doscient[oa]s|trescient[oa]s|cuatrocient[ao]s|quinient[ao]s|seiscient[ao]s|setecient[ao]s|ochocient[ao]s|novecient[ao]s|cien(to)?)"; + + public static final String WrittenOneToNinetyNineRegex = "(((treinta|cuarenta|cincuenta|sesenta|setenta|ochenta|noventa)(\\s+y\\s+{WrittenOneToNineRegex})?)|diez|once|doce|trece|catorce|quince|dieciséis|dieciseis|diecisiete|dieciocho|diecinueve|veinte|veintiuno|veintiún|veintiun|veintiuna|veintidós|veintidos|veintitrés|veintitres|veinticuatro|veinticinco|veintiséis|veintisiete|veintiocho|veintinueve|un[ao]?|dos|tres|cuatro|cinco|seis|siete|ocho|nueve)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final String FullTextYearRegex = "\\b(?((dos\\s+)?mil)(\\s+{WrittenOneHundredToNineHundredRegex})?(\\s+{WrittenOneToNinetyNineRegex})?)" + .replace("{WrittenOneToNinetyNineRegex}", WrittenOneToNinetyNineRegex) + .replace("{WrittenOneHundredToNineHundredRegex}", WrittenOneHundredToNineHundredRegex); + + public static final String YearRegex = "({BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String RelativeMonthRegex = "(?(de\\s+)?((este|pr[oó]ximo|([uú]ltim(o|as|os)))\\s+mes)|(del\\s+)?(mes\\s+((que\\s+viene)|pasado)))\\b"; + + public static final String MonthRegex = "\\b(?abr(\\.|(il)?\\b)|ago(\\.|(sto)?\\b)|dic(\\.|(iembre)?\\b)|feb(\\.|(rero)?\\b)|ene(\\.|(ro)?\\b)|ju[ln](\\.|(io)?\\b)|mar(\\.|(zo)?\\b)|may(\\.|(o)?\\b)|nov(\\.|(iembre)?\\b)|oct(\\.|(ubre)?\\b)|sep?t(\\.|(iembre)?\\b)|sep(\\.|\\b))"; + + public static final String MonthSuffixRegex = "(?((del?|la|el)\\s+)?({RelativeMonthRegex}|{MonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String DateUnitRegex = "(?años?|mes(es)?|semanas?|d[ií]as?(?\\s+(h[aá]biles|laborales))?)\\b"; + + public static final String PastRegex = "(?\\b(pasad(a|o)(s)?|[uú]ltim[oa](s)?|anterior(es)?|previo(s)?)\\b)"; + + public static final String FutureRegex = "\\b(siguiente(s)?|pr[oó]xim[oa](s)?|dentro\\s+de|en)\\b"; + + public static final String SimpleCasesRegex = "\\b((desde(\\s+el)?|entre|del?)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b{MonthSuffixRegex}\\s+((desde(\\s+el)?|entre|del)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+((entre(\\s+el)?)\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String DayBetweenRegex = "\\b((entre(\\s+el)?)\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String SpecialYearPrefixes = "((del\\s+)?calend[aá]rio|(?fiscal|escolar))"; + + public static final String OneWordPeriodRegex = "\\b(((((la|el)\\s+)?mes\\s+(({OfPrepositionRegex})\\s+)?)|((pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?)\\s+))?({MonthRegex})|(((la|el)\\s+)?((({RelativeRegex}\\s+)({DateUnitRegex}|(fin\\s+de\\s+)?semana|finde)(\\s+{RelativeSuffixRegex})?)|{DateUnitRegex}(\\s+{RelativeSuffixRegex}))|va\\s+de\\s+{DateUnitRegex}|((año|mes)|((el\\s+)?fin\\s+de\\s+)?semana|(el\\s+)?finde))\\b)" + .replace("{MonthRegex}", MonthRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{OfPrepositionRegex}", OfPrepositionRegex) + .replace("{RelativeSuffixRegex}", RelativeSuffixRegex) + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String MonthWithYearRegex = "\\b(((pr[oó]xim[oa](s)?|est?[ae]|[uú]ltim[oa]?)\\s+)?({MonthRegex})(\\s+|(\\s*[,-]\\s*))((de(l|\\s+la)?|en)\\s+)?({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|este)\\s+año))\\b" + .replace("{MonthRegex}", MonthRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthNumWithYearRegex = "\\b(({YearRegex}(\\s*?)[/\\-\\.~](\\s*?){MonthNumRegex})|({MonthNumRegex}(\\s*?)[/\\-\\.~](\\s*?){YearRegex}))\\b" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "(?(la\\s+)?(?primera?|1ra|segunda|2da|tercera?|3ra|cuarta|4ta|quinta|5ta|([12345](\\.)?ª)|[uú]ltima)\\s+semana\\s+{MonthSuffixRegex}((\\s+de)?\\s+({BaseDateTime.FourDigitYearRegex}|{RelativeRegex}\\s+año))?)\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String WeekOfYearRegex = "(?(la\\s+)?(?primera?|1ra|segunda|2da|tercera?|3ra|cuarta|4ta|quinta|5ta|[uú]ltima?|([12345]ª))\\s+semana(\\s+(del?|en))?\\s+({YearRegex}|(?pr[oó]ximo|[uú]ltimo|este)\\s+año))" + .replace("{YearRegex}", YearRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterTermRegex = "\\b((?primer|1er|segundo|2do|tercer|3ro|4to|([1234](\\.)?º))\\s+(trimestre|cuarto)|[tq](?[1-4]))\\b"; + + public static final String RelativeQuarterTermRegex = "\\b((?{StrictRelativeRegex})\\s+(trimestre|cuarto)|(trimestre|cuarto)\\s+(?(actual|pr[oó]ximo|siguiente|pasado|anterior)))\\b" + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String QuarterRegex = "(el\\s+)?{QuarterTermRegex}((\\s+(del?\\s+)?|\\s*[,-]\\s*)({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|este)\\s+a[ñn]o|a[ñn]o(\\s+{RelativeSuffixRegex}))|\\s+del\\s+a[ñn]o)?|{RelativeQuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{RelativeSuffixRegex}", RelativeSuffixRegex) + .replace("{RelativeQuarterTermRegex}", RelativeQuarterTermRegex); + + public static final String QuarterRegexYearFront = "({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|este)\\s+a[ñn]o)(?:\\s*-\\s*|\\s+(el\\s+)?)?{QuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex); + + public static final String AllHalfYearRegex = "\\b(?primer|1er|segundo|2do|[12](\\.)?º)\\s+semestre(\\s+(de\\s+)?({YearRegex}|{RelativeRegex}\\s+año))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String EarlyPrefixRegex = "\\b(?(?m[aá]s\\s+temprano(\\s+(del?|en))?)|((comienzos?|inicios?|principios?|temprano)\\s+({OfPrepositionRegex}(\\s+d[ií]a)?)))(\\s+(el|las?|los?))?\\b" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String MidPrefixRegex = "\\b(?(media[dn]os\\s+({OfPrepositionRegex})))(\\s+(el|las?|los?))?\\b" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String LaterPrefixRegex = "\\b(?((fin(al)?(es)?|[uú]ltimos)\\s+({OfPrepositionRegex}))|(?m[aá]s\\s+tarde(\\s+(del?|en))?))(\\s+(el|las?|los?))?\\b" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String PrefixPeriodRegex = "({EarlyPrefixRegex}|{MidPrefixRegex}|{LaterPrefixRegex})" + .replace("{EarlyPrefixRegex}", EarlyPrefixRegex) + .replace("{MidPrefixRegex}", MidPrefixRegex) + .replace("{LaterPrefixRegex}", LaterPrefixRegex); + + public static final String PrefixDayRegex = "\\b((?(comienzos?|inicios?|principios?|temprano))|(?mediados)|(?(fin((al)?es)?|m[aá]s\\s+tarde)))(\\s+(en|{OfPrepositionRegex}))?(\\s+([ae]l)(\\s+d[ií]a)?)?$" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String CenturySuffixRegex = "(^siglo)\\b"; + + public static final String SeasonRegex = "\\b(?(([uú]ltim[oa]|est[ea]|el|la|(pr[oó]xim[oa]s?|siguiente)|{PrefixPeriodRegex})\\s+)?(?primavera|verano|otoño|invierno)((\\s+(del?|en)|\\s*,\\s*)?\\s+({YearRegex}|(?pr[oó]ximo|[uú]ltimo|este)\\s+año))?)\\b" + .replace("{YearRegex}", YearRegex) + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex); + + public static final String WhichWeekRegex = "\\b(semana)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "((del?|el|la)\\s+)?(semana)(\\s*)({OfPrepositionRegex}|que\\s+(inicia|comienza)\\s+el|(que\\s+va|a\\s+partir)\\s+del)" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String MonthOfRegex = "(mes)(\\s+)({OfPrepositionRegex})" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String RangeUnitRegex = "\\b(?años?|mes(es)?|semanas?)\\b"; + + public static final String BeforeAfterRegex = "^[.]"; + + public static final String InConnectorRegex = "\\b(en)\\b"; + + public static final String SinceYearSuffixRegex = "^[.]"; + + public static final String WithinNextPrefixRegex = "\\b(dentro\\s+de)\\b"; + + public static final String TodayNowRegex = "\\b(hoy|ahora|este entonces)\\b"; + + public static final String FromRegex = "((\\bde(sde)?)(\\s*la(s)?)?)$"; + + public static final String BetweenRegex = "(\\bentre\\s*(la(s)?)?)"; + + public static final String WeekDayRegex = "\\b(?(domingos?|lunes|martes|mi[eé]rcoles|jueves|viernes|s[aá]bados?)\\b|(lun|mar|mi[eé]|jue|vie|s[aá]b|dom|lu|ma|mi|ju|vi|s[aá]|do)(\\.|\\b))(?!ñ)"; + + public static final String OnRegex = "((?<=\\b(e[ln])\\s+)|(\\be[ln]\\s+d[ií]a\\s+))({DayRegex}s?)(?![.,]\\d)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String RelaxedOnRegex = "(?<=\\b(en|d?el)\\s+)((?10|11|12|13|14|15|16|17|18|19|1st|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)s?)(?![.,]\\d)\\b"; + + public static final String SpecialDayRegex = "\\b((el\\s+)?(d[ií]a\\s+antes\\s+de\\s+ayer|anteayer)|((el\\s+)?d[ií]a\\s+(despu[eé]s\\s+)?de\\s+mañana|pasado\\s+mañana)|(el\\s)?d[ií]a\\s+(siguiente|anterior)|(el\\s)?pr[oó]ximo\\s+d[ií]a|(el\\s+)?[uú]ltimo\\s+d[ií]a|(d)?el\\s+d[ií]a(?!\\s+d)|ayer|mañana|hoy)\\b"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String FlexibleDayRegex = "(?([A-Za-z]+\\s)?({WrittenDayRegex}|{DayRegex}))" + .replace("{WrittenDayRegex}", WrittenDayRegex) + .replace("{DayRegex}", DayRegex); + + public static final String ForTheRegex = "\\b((((?<=para\\s+el\\s+){FlexibleDayRegex})|((?\\s*(,|\\.(?![º°ª])|!|\\?|-|$))(?!\\d))" + .replace("{FlexibleDayRegex}", FlexibleDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayAndDayOfMonthRegex = "\\b{WeekDayRegex}\\s+((el\\s+(d[ií]a\\s+)?){FlexibleDayRegex})\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayRegex = "\\b{WeekDayRegex}\\s+({DayRegex}|{WrittenDayRegex})(?!([-:/]|\\.\\d|(\\s+({AmDescRegex}|{PmDescRegex}|{OclockRegex}))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WrittenDayRegex}", WrittenDayRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String WeekDayOfMonthRegex = "(?(el\\s+)?(?primera?|1era?|segund[ao]|2d[ao]|tercera?|3era?|cuart[ao]|4t[ao]|quint[ao]|5t[ao]|((1|2|3|4|5)(\\.)?[ºª])|[uú]ltim[ao])\\s+(semana\\s+{MonthSuffixRegex}\\s+el\\s+{WeekDayRegex}|{WeekDayRegex}\\s+{MonthSuffixRegex}))" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "^[.]"; + + public static final String AmbiguousRangeModifierPrefix = "^[.]"; + + public static final String NumberEndingPattern = "^[.]"; + + public static final String DateTokenPrefix = "en "; + + public static final String TimeTokenPrefix = "a las "; + + public static final String TokenBeforeDate = "el "; + + public static final String TokenBeforeTime = "a las "; + + public static final String HalfTokenRegex = "^((y\\s+)?media)"; + + public static final String QuarterTokenRegex = "^((y\\s+)?cuarto|(?menos\\s+cuarto))"; + + public static final String PastTokenRegex = "\\b(pasad[ao]s(\\s+(de\\s+)?las)?)$"; + + public static final String ToTokenRegex = "\\b((para|antes)(\\s+(de\\s+)?las?)|(?^menos))$"; + + public static final String SpecialDateRegex = "(?<=\\b(en)\\s+el\\s+){DayRegex}\\b" + .replace("{DayRegex}", DayRegex); + + public static final String OfMonthRegex = "^\\s*((d[ií]a\\s+)?d[eo]\\s+)?{MonthSuffixRegex}" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String MonthEndRegex = "({MonthRegex}\\s*(el)?\\s*$)" + .replace("{MonthRegex}", MonthRegex); + + public static final String WeekDayEnd = "{WeekDayRegex}\\s*,?\\s*$" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayStart = "^[\\.]"; + + public static final String DateYearRegex = "(?{YearRegex}|(?cero|una|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce)\\b"; + + public static final String MinuteNumRegex = "(?uno?|d[óo]s|tr[eé]s|cuatro|cinco|s[eé]is|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|diecis[eé]is|diecisiete|dieciocho|diecinueve|veinte|treinta|cuarenta|cincuenta)"; + + public static final String DeltaMinuteNumRegex = "(?uno?|d[óo]s|tr[eé]s|cuatro|cinco|s[eé]is|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|diecis[eé]is|diecisiete|dieciocho|diecinueve|veinte|treinta|cuarenta|cincuenta)"; + + public static final String PmRegex = "(?((por|de|a|en)\\s+la)\\s+(tarde|noche))"; + + public static final String AmRegex = "(?((por|de|a|en)\\s+la)\\s+(mañana|madrugada))"; + + public static final String AmTimeRegex = "(?(esta|(por|de|a|en)\\s+la)\\s+(mañana|madrugada))"; + + public static final String PmTimeRegex = "(?(esta|(por|de|a|en)\\s+la)\\s+(tarde|noche))"; + + public static final String LessThanOneHour = "(?((\\s+y\\s+)?cuarto|(\\s*)menos cuarto|(\\s+y\\s+)media|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutos?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutos?|mins?))))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String TensTimeRegex = "(?diez|veint(i|e)|treinta|cuarenta|cincuenta)"; + + public static final String WrittenTimeRegex = "(?{HourNumRegex}\\s*((y|(?menos))\\s+)?(({TensTimeRegex}(\\s*y\\s+)?)?{MinuteNumRegex}))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex); + + public static final String TimePrefix = "(?{LessThanOneHour}(\\s+(pasad[ao]s)\\s+(de\\s+las|las)?|\\s+(para|antes\\s+de)?\\s+(las?))?)" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?({LessThanOneHour}\\s+)?({AmRegex}|{PmRegex}|{OclockRegex}))" + .replace("{LessThanOneHour}", LessThanOneHour) + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex})" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String MidTimeRegex = "(?((?media\\s*noche)|(?media\\s*mañana)|(?media\\s*tarde)|(?medio\\s*d[ií]a)))"; + + public static final String AtRegex = "\\b((?<=\\b((a|de(sde)?)\\s+las?|al)\\s+)(({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\b(\\s*\\bh\\b)?(DescRegex)?|{MidTimeRegex})|{MidTimeRegex})" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{DescRegex}", DescRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String ConnectNumRegex = "({BaseDateTime.HourRegex}(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)\\s*{DescRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegexWithDotConnector = "({BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex); + + public static final String TimeRegex1 = "(\\b{TimePrefix}\\s+)?({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\s*({DescRegex}|\\s*\\bh\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "\\b(({TimePrefix}\\s+)?{TimeRegexWithDotConnector}(\\s*({DescRegex}|{TimeSuffix}|\\bh\\b))|((las\\s+{TimeRegexWithDotConnector})(?!\\s*(por\\s+cien(to)?|%))(\\s*({DescRegex}|{TimeSuffix}|\\bh\\b)|\\b)))" + .replace("{TimePrefix}", TimePrefix) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{DescRegex}", DescRegex) + .replace("{TimeTokenPrefix}", TimeTokenPrefix) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex4 = "\\b(({DescRegex}?)|({BasicTime}?)({DescRegex}?)){TimePrefix}(\\s*({HourNumRegex}|{BaseDateTime.HourRegex}))?(\\s+{TensTimeRegex}(\\s*(y\\s+)?{MinuteNumRegex})?)?(\\s*({OclockRegex}|{DescRegex})|\\b)" + .replace("{DescRegex}", DescRegex) + .replace("{BasicTime}", BasicTime) + .replace("{TimePrefix}", TimePrefix) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String TimeRegex5 = "\\b({TimePrefix}|{BasicTime}{TimePrefix})\\s+(\\s*{DescRegex})?{BasicTime}?\\s*{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex6 = "({BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b)" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffix}\\s+a\\s+las\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = "\\b{TimeSuffix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b(?{HourNumRegex}\\s+({TensTimeRegex}\\s*)(y\\s+)?{MinuteNumRegex}?)\\b" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimeRegex10 = "(a\\s+la|al)\\s+(madrugada|mañana|tarde|noche)"; + + public static final String TimeRegex11 = "\\b({WrittenTimeRegex})(\\s+{DescRegex})?\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex12 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String PrepositionRegex = "(?^(,\\s*)?(a(l)?|en|de(l)?)?(\\s*(la(s)?|el|los))?$)"; + + public static final String LaterEarlyRegex = "((?temprano)|(?fin(al)?(\\s+de)?|m[aá]s\\s+tarde))"; + + public static final String NowRegex = "\\b(?(justo\\s+)?ahora(\\s+mismo)?|en\\s+este\\s+momento|tan\\s+pronto\\s+como\\s+sea\\s+posible|tan\\s+pronto\\s+como\\s+(pueda|puedas|podamos|puedan)|lo\\s+m[aá]s\\s+pronto\\s+posible|recientemente|previamente|este entonces)\\b"; + + public static final String SuffixRegex = "^\\s*(((y|a|en|por)\\s+la|al)\\s+)?(mañana|madrugada|medio\\s*d[ií]a|(?(({LaterEarlyRegex}\\s+)((del?|en|por)(\\s+(el|los?|las?))?\\s+)?)?(mañana|madrugada|pasado\\s+(el\\s+)?medio\\s?d[ií]a|(?mañana|madrugada|(?pasado\\s+(el\\s+)?medio\\s?d[ií]a|tarde|noche))\\b"; + + public static final String PeriodTimeOfDayRegex = "\\b((en\\s+(el|la|lo)?\\s+)?({LaterEarlyRegex}\\s+)?(est[ae]\\s+)?{DateTimeTimeOfDayRegex})\\b" + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{LaterEarlyRegex}", LaterEarlyRegex); + + public static final String PeriodSpecificTimeOfDayRegex = "\\b(({LaterEarlyRegex}\\s+)?est[ae]\\s+{DateTimeTimeOfDayRegex}|({StrictRelativeRegex}\\s+{PeriodTimeOfDayRegex})|anoche)\\b" + .replace("{PeriodTimeOfDayRegex}", PeriodTimeOfDayRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex) + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{LaterEarlyRegex}", LaterEarlyRegex); + + public static final String UnitRegex = "(?años?|(bi|tri|cuatri|se)mestre|mes(es)?|semanas?|fin(es)?\\s+de\\s+semana|finde|d[ií]as?|horas?|hra?s?|hs?|minutos?|mins?|segundos?|segs?|noches?)\\b"; + + public static final String ConnectorRegex = "^(,|t|(para|y|a|en|por) las?|(\\s*,\\s*)?(cerca|alrededor) de las?)$"; + + public static final String TimeHourNumRegex = "(?veintiuno|veintidos|veintitres|veinticuatro|cero|uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|diecis([eé])is|diecisiete|dieciocho|diecinueve|veinte)"; + + public static final String PureNumFromTo = "((\\b(desde|de)\\s+(la(s)?\\s+)?)?({BaseDateTime.HourRegex}|{TimeHourNumRegex})(?!\\s+al?\\b)(\\s*(?{DescRegex}))?|(\\b(desde|de)\\s+(la(s)?\\s+)?)({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?)\\s*{TillRegex}\\s*({BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String PureNumBetweenAnd = "(\\bentre\\s+(la(s)?\\s+)?)(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?\\s*{RangeConnectorRegex}\\s*(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{BaseDateTime.TwoDigitHourRegex}", BaseDateTime.TwoDigitHourRegex) + .replace("{BaseDateTime.TwoDigitMinuteRegex}", BaseDateTime.TwoDigitMinuteRegex); + + public static final String SpecificTimeFromTo = "({RangePrefixRegex}\\s+)?(?(({TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{TillRegex}\\s*(?(({TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{TimeRegex2}", TimeRegex2) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{TillRegex}", TillRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String SpecificTimeBetweenAnd = "({BetweenRegex}\\s+)(?(({TimeRegex1}|{TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{RangeConnectorRegex}\\s*(?(({TimeRegex1}|{TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{BetweenRegex}", BetweenRegex) + .replace("{TimeRegex1}", TimeRegex1) + .replace("{TimeRegex2}", TimeRegex2) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeUnitRegex = "([^A-Za-z]{1,}|\\b)(?horas?|h|minutos?|mins?|segundos?|se[cg]s?)\\b"; + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String DateTimePeriodNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String PeriodTimeOfDayWithDateRegex = "\\b(((y|a|en|por)\\s+(la\\s+)?|al\\s+)?((((?primeras\\s+horas\\s+)|(?(últimas|altas)\\s+horas\\s+))(de\\s+la\\s+)?|{LaterEarlyRegex}\\s+(est[ae]\\s+)?)?(?(mañana|madrugada|pasado\\s+(el\\s+)?medio\\s?d[ií]a|(?\\s*(y)\\s+((un[ao]?)\\s+)?(?media|cuarto))"; + + public static final String FollowedUnit = "^\\s*{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DurationNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?){UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String AnUnitRegex = "\\b(una?|otr[ao])\\s+{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DuringRegex = "^[.]"; + + public static final String AllRegex = "\\b(?tod[oa]?\\s+(el|la)\\s+(?año|mes|semana|d[ií]a)|((una?|el|la)\\s+)?(?año|mes|semana|d[ií]a)\\s+enter[ao])\\b"; + + public static final String HalfRegex = "\\b(?medi[oa]\\s+(?ano|mes|semana|d[íi]a|hora))\\b"; + + public static final String ConjunctionRegex = "^[.]"; + + public static final String InexactNumberRegex = "\\b(pocos?|algo|vari[ao]s|algun[ao]s|un[ao]s)\\b"; + + public static final String InexactNumberUnitRegex = "({InexactNumberRegex})\\s+{UnitRegex}" + .replace("{InexactNumberRegex}", InexactNumberRegex) + .replace("{UnitRegex}", UnitRegex); + + public static final String HolidayRegex1 = "\\b(?viernes santo|mi[eé]rcoles de ceniza|martes de carnaval|d[ií]a (de|de los) presidentes?|clebraci[oó]n de mao|año nuevo chino|año nuevo|noche vieja|(festividad de )?los mayos|d[ií]a de los inocentes|navidad|noche buena|d[ií]a de acci[oó]n de gracias|acci[oó]n de gracias|yuandan|halloween|noches de brujas|pascuas)(\\s+(del?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?|en))\\s+año))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex2 = "\\b(?(d[ií]a( del?( la)?)? )?(martin luther king|todos los santos|blanco|san patricio|san valent[ií]n|san jorge|cinco de mayo|independencia|raza|trabajador))(\\s+(del?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?|en))\\s+año))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex3 = "\\b(?(d[ií]a( internacional)?( del?( l[ao]s?)?)? )(trabajador(es)?|madres?|padres?|[aá]rbol|mujer(es)?|solteros?|niños?|marmota|san valent[ií]n|maestro))(\\s+(del?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?|en))\\s+año))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String BeforeRegex = "(\\b((ante(s|rior)|m[aá]s\\s+temprano|no\\s+m[aá]s\\s+tard(e|ar)|(?tan\\s+tarde\\s+como))(\\s+(del?|a|que)(\\s+(el|las?|los?))?)?)|(?)((?<\\s*=)|<))"; + + public static final String AfterRegex = "((\\b(despu[eé]s|(año\\s+)?posterior|m[aá]s\\s+tarde|a\\s+primeros)(\\s*(del?|en|a)(\\s+(el|las?|los?))?)?|(empi?en?zando|comenzando)(\\s+(el|las?|los?))?)\\b|(?>\\s*=)|>))"; + + public static final String SinceRegex = "\\b(((cualquier\\s+tiempo\\s+)?(desde|a\\s+partir\\s+del?)|tan\\s+(temprano|pronto)\\s+como(\\s+(de|a))?)(\\s+(el|las?|los?))?)\\b"; + + public static final String SinceRegexExp = "({SinceRegex}|\\bde\\b)" + .replace("{SinceRegex}", SinceRegex); + + public static final String AroundRegex = "(?:\\b(?:cerca|alrededor|aproximadamente)(\\s+(de\\s+(las?|el)|del?))?\\s*\\b)"; + + public static final String PeriodicRegex = "\\b(?a\\s*diario|diaria(s|mente)|(bi|tri)?(semanal|quincenal|mensual|semestral|anual)(es|mente)?)\\b"; + + public static final String EachExpression = "\\b(cada|tod[oa]s\\s*(l[oa]s)?)\\b\\s*(?!\\s*l[oa]\\b)"; + + public static final String EachUnitRegex = "(?({EachExpression})\\s*({UnitRegex}|(?fin(es)?\\s+de\\s+semana|finde)\\b))" + .replace("{EachExpression}", EachExpression) + .replace("{UnitRegex}", UnitRegex); + + public static final String EachPrefixRegex = "(?({EachExpression})\\s*$)" + .replace("{EachExpression}", EachExpression); + + public static final String EachDayRegex = "\\s*({EachExpression})\\s*d[ií]as\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String BeforeEachDayRegex = "({EachExpression})\\s*d[ií]as(\\s+a\\s+las?)?\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String SetEachRegex = "(?({EachExpression})\\s*)" + .replace("{EachExpression}", EachExpression); + + public static final String LaterEarlyPeriodRegex = "\\b(({PrefixPeriodRegex})\\s+(?{OneWordPeriodRegex}|(?{BaseDateTime.FourDigitYearRegex}))|({UnspecificEndOfRangeRegex}))\\b" + .replace("{OneWordPeriodRegex}", OneWordPeriodRegex) + .replace("{UnspecificEndOfRangeRegex}", UnspecificEndOfRangeRegex) + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex); + + public static final String RelativeWeekRegex = "(((la|el)\\s+)?(((est[ae]|pr[oó]xim[oa]|[uú]ltim(o|as|os))\\s+semanas?)|(semanas?\\s+(que\\s+viene|pasad[oa]))))"; + + public static final String WeekWithWeekDayRangeRegex = "\\b((({RelativeWeekRegex})((\\s+entre\\s+{WeekDayRegex}\\s+y\\s+{WeekDayRegex})|(\\s+de\\s+{WeekDayRegex}\\s+a\\s+{WeekDayRegex})))|((entre\\s+{WeekDayRegex}\\s+y\\s+{WeekDayRegex})|(de\\s+{WeekDayRegex}\\s+a\\s+{WeekDayRegex})){OfPrepositionRegex}\\s+{RelativeWeekRegex})\\b" + .replace("{RelativeWeekRegex}", RelativeWeekRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String GeneralEndingRegex = "^\\s*((\\.,)|\\.|,|!|\\?)?\\s*$"; + + public static final String MiddlePauseRegex = "^[.]"; + + public static final String PrefixArticleRegex = "\\b(e[ln]\\s+(d[ií]a\\s+)?)"; + + public static final String OrRegex = "^[.]"; + + public static final String SpecialYearTermsRegex = "\\b(años?\\s+({SpecialYearPrefixes}\\s+)?(de\\s+)?)" + .replace("{SpecialYearPrefixes}", SpecialYearPrefixes); + + public static final String YearPlusNumberRegex = "\\b({SpecialYearTermsRegex}((?(\\d{2,4}))|{FullTextYearRegex}))\\b" + .replace("{FullTextYearRegex}", FullTextYearRegex) + .replace("{SpecialYearTermsRegex}", SpecialYearTermsRegex); + + public static final String NumberAsTimeRegex = "^[.]"; + + public static final String TimeBeforeAfterRegex = "\\b((?<=\\b(antes|no\\s+m[aá]s\\s+tard(e|ar)\\s+(de|a\\s+las?)|por| después)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}|{MidTimeRegex}))\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String DateNumberConnectorRegex = "^[.]"; + + public static final String CenturyRegex = "^[.]"; + + public static final String DecadeRegex = "(?diez|veinte|treinta|cuarenta|cincuenta|se[st]enta|ochenta|noventa)"; + + public static final String DecadeWithCenturyRegex = "(los\\s+)?((((d[ée]cada(\\s+de)?)\\s+)(((?\\d|1\\d|2\\d)?(?\\d0))))|a[ñn]os\\s+((((dos\\s+)?mil\\s+)?({WrittenOneHundredToNineHundredRegex}\\s+)?{DecadeRegex})|((dos\\s+)?mil\\s+)?({WrittenOneHundredToNineHundredRegex})(\\s+{DecadeRegex}?)|((dos\\s+)?mil)(\\s+{WrittenOneHundredToNineHundredRegex}\\s+)?{DecadeRegex}?))" + .replace("{WrittenOneHundredToNineHundredRegex}", WrittenOneHundredToNineHundredRegex) + .replace("{DecadeRegex}", DecadeRegex); + + public static final String RelativeDecadeRegex = "\\b(((el|las?)\\s+)?{RelativeRegex}\\s+(((?[\\d]+)|{WrittenOneToNineRegex})\\s+)?d[eé]cadas?)\\b" + .replace("{RelativeRegex}", RelativeRegex) + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final String ComplexDatePeriodRegex = "(?:((de(sde)?)\\s+)?(?.+)\\s*({StrictTillRegex})\\s*(?.+)|((entre)\\s+)(?.+)\\s*({RangeConnectorRegex})\\s*(?.+))" + .replace("{StrictTillRegex}", StrictTillRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String AmbiguousPointRangeRegex = "^(mar\\.?)$"; + + public static final String YearSuffix = "((,|\\sde)?\\s*({YearRegex}|{FullTextYearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String AgoRegex = "\\b(antes\\s+de\\s+(?hoy|ayer|mañana)|antes)\\b"; + + public static final String LaterRegex = "\\b(despu[eé]s(?!\\s+de\\b)|desde\\s+ahora|a\\s+partir\\s+de\\s+(?hoy|ayer|mañana))\\b"; + + public static final String Tomorrow = "mañana"; + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("años", "Y") + .put("año", "Y") + .put("meses", "MON") + .put("mes", "MON") + .put("trimestre", "3MON") + .put("trimestres", "3MON") + .put("cuatrimestre", "4MON") + .put("cuatrimestres", "4MON") + .put("semestre", "6MON") + .put("semestres", "6MON") + .put("bimestre", "2MON") + .put("bimestres", "2MON") + .put("semanas", "W") + .put("semana", "W") + .put("fin de semana", "WE") + .put("fines de semana", "WE") + .put("finde", "WE") + .put("dias", "D") + .put("dia", "D") + .put("días", "D") + .put("día", "D") + .put("jornada", "D") + .put("noche", "D") + .put("noches", "D") + .put("horas", "H") + .put("hora", "H") + .put("hrs", "H") + .put("hras", "H") + .put("hra", "H") + .put("hr", "H") + .put("h", "H") + .put("minutos", "M") + .put("minuto", "M") + .put("mins", "M") + .put("min", "M") + .put("segundos", "S") + .put("segundo", "S") + .put("segs", "S") + .put("seg", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("años", 31536000L) + .put("año", 31536000L) + .put("meses", 2592000L) + .put("mes", 2592000L) + .put("semanas", 604800L) + .put("semana", 604800L) + .put("fin de semana", 172800L) + .put("fines de semana", 172800L) + .put("finde", 172800L) + .put("dias", 86400L) + .put("dia", 86400L) + .put("días", 86400L) + .put("día", 86400L) + .put("noche", 86400L) + .put("noches", 86400L) + .put("horas", 3600L) + .put("hora", 3600L) + .put("hrs", 3600L) + .put("hras", 3600L) + .put("hra", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutos", 60L) + .put("minuto", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("segundos", 1L) + .put("segundo", 1L) + .put("segs", 1L) + .put("seg", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("fiscal", "FY") + .put("escolar", "SY") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("primavera", "SP") + .put("verano", "SU") + .put("otoño", "FA") + .put("invierno", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("primer", 1) + .put("primero", 1) + .put("primera", 1) + .put("1er", 1) + .put("1ro", 1) + .put("1ra", 1) + .put("1.º", 1) + .put("1º", 1) + .put("1ª", 1) + .put("segundo", 2) + .put("segunda", 2) + .put("2do", 2) + .put("2da", 2) + .put("2.º", 2) + .put("2º", 2) + .put("2ª", 2) + .put("tercer", 3) + .put("tercero", 3) + .put("tercera", 3) + .put("3er", 3) + .put("3ro", 3) + .put("3ra", 3) + .put("3.º", 3) + .put("3º", 3) + .put("3ª", 3) + .put("cuarto", 4) + .put("cuarta", 4) + .put("4to", 4) + .put("4ta", 4) + .put("4.º", 4) + .put("4º", 4) + .put("4ª", 4) + .put("quinto", 5) + .put("quinta", 5) + .put("5to", 5) + .put("5ta", 5) + .put("5.º", 5) + .put("5º", 5) + .put("5ª", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("lunes", 1) + .put("martes", 2) + .put("miercoles", 3) + .put("miércoles", 3) + .put("jueves", 4) + .put("viernes", 5) + .put("sabado", 6) + .put("sábado", 6) + .put("domingo", 0) + .put("dom", 0) + .put("lun", 1) + .put("mar", 2) + .put("mie", 3) + .put("mié", 3) + .put("jue", 4) + .put("vie", 5) + .put("sab", 6) + .put("sáb", 6) + .put("dom.", 0) + .put("lun.", 1) + .put("mar.", 2) + .put("mie.", 3) + .put("mié.", 3) + .put("jue.", 4) + .put("vie.", 5) + .put("sab.", 6) + .put("sáb.", 6) + .put("do", 0) + .put("lu", 1) + .put("ma", 2) + .put("mi", 3) + .put("ju", 4) + .put("vi", 5) + .put("sa", 6) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("enero", 1) + .put("febrero", 2) + .put("marzo", 3) + .put("abril", 4) + .put("mayo", 5) + .put("junio", 6) + .put("julio", 7) + .put("agosto", 8) + .put("septiembre", 9) + .put("setiembre", 9) + .put("octubre", 10) + .put("noviembre", 11) + .put("diciembre", 12) + .put("ene", 1) + .put("feb", 2) + .put("mar", 3) + .put("abr", 4) + .put("may", 5) + .put("jun", 6) + .put("jul", 7) + .put("ago", 8) + .put("sept", 9) + .put("sep", 9) + .put("set", 9) + .put("oct", 10) + .put("nov", 11) + .put("dic", 12) + .put("ene.", 1) + .put("feb.", 2) + .put("mar.", 3) + .put("abr.", 4) + .put("may.", 5) + .put("jun.", 6) + .put("jul.", 7) + .put("ago.", 8) + .put("sept.", 9) + .put("sep.", 9) + .put("set.", 9) + .put("oct.", 10) + .put("nov.", 11) + .put("dic.", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("cero", 0) + .put("un", 1) + .put("una", 1) + .put("uno", 1) + .put("dos", 2) + .put("dós", 2) + .put("tres", 3) + .put("trés", 3) + .put("cuatro", 4) + .put("cinco", 5) + .put("seis", 6) + .put("séis", 6) + .put("siete", 7) + .put("ocho", 8) + .put("nueve", 9) + .put("diez", 10) + .put("once", 11) + .put("doce", 12) + .put("docena", 12) + .put("docenas", 12) + .put("trece", 13) + .put("catorce", 14) + .put("quince", 15) + .put("dieciseis", 16) + .put("dieciséis", 16) + .put("diecisiete", 17) + .put("dieciocho", 18) + .put("diecinueve", 19) + .put("veinte", 20) + .put("veinti", 20) + .put("ventiuna", 21) + .put("ventiuno", 21) + .put("veintiun", 21) + .put("veintiún", 21) + .put("veintiuno", 21) + .put("veintiuna", 21) + .put("veintidos", 22) + .put("veintidós", 22) + .put("veintitres", 23) + .put("veintitrés", 23) + .put("veinticuatro", 24) + .put("veinticinco", 25) + .put("veintiseis", 26) + .put("veintiséis", 26) + .put("veintisiete", 27) + .put("veintiocho", 28) + .put("veintinueve", 29) + .put("treinta", 30) + .put("cuarenta", 40) + .put("cincuenta", 50) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("padres", new String[]{"diadelpadre"}) + .put("madres", new String[]{"diadelamadre"}) + .put("acciondegracias", new String[]{"diadegracias", "diadeacciondegracias", "acciondegracias"}) + .put("trabajador", new String[]{"diadeltrabajador", "diainternacionaldelostrabajadores"}) + .put("delaraza", new String[]{"diadelaraza", "diadeladiversidadcultural"}) + .put("memoria", new String[]{"diadelamemoria"}) + .put("pascuas", new String[]{"diadepascuas", "pascuas"}) + .put("navidad", new String[]{"navidad", "diadenavidad"}) + .put("nochebuena", new String[]{"diadenochebuena", "nochebuena"}) + .put("añonuevo", new String[]{"añonuevo", "diadeañonuevo"}) + .put("nochevieja", new String[]{"nochevieja", "diadenochevieja"}) + .put("yuandan", new String[]{"yuandan"}) + .put("earthday", new String[]{"diadelatierra"}) + .put("maestro", new String[]{"diadelmaestro"}) + .put("todoslossantos", new String[]{"todoslossantos"}) + .put("niño", new String[]{"diadelniño"}) + .put("mujer", new String[]{"diadelamujer"}) + .put("independencia", new String[]{"diadelaindependencia", "diadeindependencia", "independencia"}) + .build(); + + public static final ImmutableMap VariableHolidaysTimexDictionary = ImmutableMap.builder() + .put("padres", "-06-WXX-7-3") + .put("madres", "-05-WXX-7-2") + .put("acciondegracias", "-11-WXX-4-4") + .put("delaraza", "-10-WXX-1-2") + .put("memoria", "-03-WXX-2-4") + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("mitad", 0.5D) + .put("cuarto", 0.25D) + .build(); + + public static final String UpcomingPrefixRegex = "((este\\s+))"; + + public static final String NextPrefixRegex = "\\b({UpcomingPrefixRegex}?pr[oó]xim[oa]s?|siguiente|que\\s+viene)\\b" + .replace("{UpcomingPrefixRegex}", UpcomingPrefixRegex); + + public static final String PastPrefixRegex = "((este\\s+))"; + + public static final String PreviousPrefixRegex = "\\b({PastPrefixRegex}?pasad[oa](?!(\\s+el)?\\s+medio\\s*d[ií]a)|[uú]ltim[oa]|anterior)\\b" + .replace("{PastPrefixRegex}", PastPrefixRegex); + + public static final String ThisPrefixRegex = "(est?[ea]|actual)\\b"; + + public static final String PrefixWeekDayRegex = "(\\s*((,?\\s*el)|[-—–]))"; + + public static final String ThisRegex = "\\b((est[ae]\\s*)(semana{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|({WeekDayRegex}\\s*((de\\s+)?esta\\s+semana))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String LastDateRegex = "\\b(({PreviousPrefixRegex}\\s+(semana{PrefixWeekDayRegex}?)?|(la\\s+)?semana\\s+{PreviousPrefixRegex}{PrefixWeekDayRegex})\\s*{WeekDayRegex})|(este\\s+)?({WeekDayRegex}\\s+([uú]ltimo|pasado|anterior))|({WeekDayRegex}(\\s+((de\\s+)?((esta|la)\\s+([uú]ltima\\s+)?semana)|(de\\s+)?(la\\s+)?semana\\s+(pasada|anterior))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String NextDateRegex = "\\b((({NextPrefixRegex}\\s+)(semana{PrefixWeekDayRegex}?)?|(la\\s+)?semana\\s+{NextPrefixRegex}{PrefixWeekDayRegex})\\s*{WeekDayRegex})|(este\\s+)?({WeekDayRegex}\\s+(pr[oó]ximo|siguiente|que\\s+viene))|({WeekDayRegex}(\\s+(de\\s+)?(la\\s+)?((pr[oó]xima|siguiente)\\s+semana|semana\\s+(pr[oó]xima|siguiente))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String RelativeDayRegex = "(?((este|pr[oó]ximo|([uú]ltim(o|as|os)))\\s+días)|(días\\s+((que\\s+viene)|pasado)))\\b"; + + public static final String RestOfDateRegex = "\\bresto\\s+((del|de)\\s+)?((la|el|est?[ae])\\s+)?(?semana|mes|año|decada)(\\s+actual)?\\b"; + + public static final String DurationUnitRegex = "(?{DateUnitRegex}|horas?|hra?s?|hs?|minutos?|mins?|segundos?|segs?|noches?)\\b" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String DurationConnectorRegex = "^[.]"; + + public static final String RelativeDurationUnitRegex = "(?:(?<=({NextPrefixRegex}|{PreviousPrefixRegex}|{ThisPrefixRegex})\\s+)({DurationUnitRegex}))" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String ReferencePrefixRegex = "(mism[ao]|aquel|est?e)\\b"; + + public static final String ReferenceDatePeriodRegex = "\\b{ReferencePrefixRegex}\\s+({DateUnitRegex}|fin\\s+de\\s+semana)\\b" + .replace("{ReferencePrefixRegex}", ReferencePrefixRegex) + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String FromToRegex = "\\b(from).+(to)\\b.+"; + + public static final String SingleAmbiguousMonthRegex = "^(the\\s+)?(may|march)$"; + + public static final String UnspecificDatePeriodRegex = "^[\\.]"; + + public static final String PrepositionSuffixRegex = "\\b(en|el|la|cerca|desde|durante|hasta|hacia)$"; + + public static final String RestOfDateTimeRegex = "\\bresto\\s+((del?)\\s+)?((la|el|est[ae])\\s+)?(?(día|jornada))(\\s+de\\s+hoy)?\\b"; + + public static final String SetWeekDayRegex = "^[\\.]"; + + public static final String NightRegex = "\\b(medionoche|noche)\\b"; + + public static final String CommonDatePrefixRegex = "^[\\.]"; + + public static final String SuffixAfterRegex = "\\b((a\\s+)?(o|y)\\s+(arriba|despu[eé]s|posterior|mayor|m[aá]s\\s+tarde)(?!\\s+(que|de)))\\b"; + + public static final String YearPeriodRegex = "((((de(sde)?|durante|en)\\s+)?{YearRegex}\\s*({TillRegex})\\s*{YearRegex})|(((entre)\\s+){YearRegex}\\s*({RangeConnectorRegex})\\s*{YearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{TillRegex}", TillRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String FutureSuffixRegex = "\\b(siguiente(s)?|pr[oó]xim[oa](s)?|(en\\s+el\\s+)?futuro|a\\s+partir\\s+de\\s+ahora)\\b"; + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final String DefaultLanguageFallback = "DMY"; + + public static final List DurationDateRestrictions = Arrays.asList("hoy"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^mi$", "\\bmi\\b") + .put("^a[nñ]o$", "(? EarlyMorningTermList = Arrays.asList("madrugada"); + + public static final List MorningTermList = Arrays.asList("mañana", "la mañana"); + + public static final List AfternoonTermList = Arrays.asList("pasado mediodia", "pasado el mediodia", "pasado mediodía", "pasado el mediodía", "pasado medio dia", "pasado el medio dia", "pasado medio día", "pasado el medio día"); + + public static final List EveningTermList = Arrays.asList("tarde"); + + public static final List NightTermList = Arrays.asList("noche"); + + public static final List SameDayTerms = Arrays.asList("hoy", "el dia"); + + public static final List PlusOneDayTerms = Arrays.asList("mañana", "dia siguiente", "el dia de mañana", "proximo dia"); + + public static final List MinusOneDayTerms = Arrays.asList("ayer", "ultimo dia", "dia anterior"); + + public static final List PlusTwoDayTerms = Arrays.asList("pasado mañana", "dia despues de mañana"); + + public static final List MinusTwoDayTerms = Arrays.asList("anteayer", "dia antes de ayer"); + + public static final List MonthTerms = Arrays.asList("mes", "meses"); + + public static final List MonthToDateTerms = Arrays.asList("mes a la fecha", "meses a la fecha"); + + public static final List WeekendTerms = Arrays.asList("finde", "fin de semana", "fines de semana"); + + public static final List WeekTerms = Arrays.asList("semana"); + + public static final List YearTerms = Arrays.asList("año", "años"); + + public static final List YearToDateTerms = Arrays.asList("año a la fecha", "años a la fecha"); + + public static final ImmutableMap SpecialCharactersEquivalent = ImmutableMap.builder() + .put('á', 'a') + .put('é', 'e') + .put('í', 'i') + .put('ó', 'o') + .put('ú', 'u') + .build(); + + public static final String DoubleMultiplierRegex = "^(bi)(-|\\s)?"; + + public static final String DayTypeRegex = "(d[ií]as?|diari(o|as|amente))$"; + + public static final String WeekTypeRegex = "(semanas?|semanalmente)$"; + + public static final String BiWeekTypeRegex = "(quincenalmente)$"; + + public static final String WeekendTypeRegex = "(fin(es)?\\s+de\\s+semana|finde)$"; + + public static final String MonthTypeRegex = "(mes(es)?|mensual(es|mente)?)$"; + + public static final String YearTypeRegex = "(años?|anualmente)$"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java new file mode 100644 index 000000000..e8735c5bb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java @@ -0,0 +1,245 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + + +public class SpanishDateExtractorConfiguration extends BaseOptionsConfiguration implements IDateExtractorConfiguration { + + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthNumRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearRegex); + public static final Pattern WeekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayRegex); + public static final Pattern OnRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OnRegex); + public static final Pattern RelaxedOnRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelaxedOnRegex); + public static final Pattern ThisRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ThisRegex); + public static final Pattern LastDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LastDateRegex); + public static final Pattern NextDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextDateRegex); + public static final Pattern SpecialDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecialDayRegex); + public static final Pattern SpecialDayWithNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecialDayWithNumRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + public static final Pattern WeekDayOfMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayOfMonthRegex); + public static final Pattern SpecialDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecialDateRegex); + public static final Pattern RelativeWeekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeWeekDayRegex); + public static final Pattern ForTheRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ForTheRegex); + public static final Pattern WeekDayAndDayOfMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayAndDayOfMonthRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeMonthRegex); + public static final Pattern StrictRelativeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.StrictRelativeRegex); + public static final Pattern PrefixArticleRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrefixArticleRegex); + public static final Pattern RangeConnectorSymbolRegex = RegExpUtility.getSafeRegExp(BaseDateTime.RangeConnectorSymbolRegex); + public static final List ImplicitDateList = new ArrayList() { + { + add(OnRegex); + add(RelaxedOnRegex); + add(SpecialDayRegex); + add(ThisRegex); + add(LastDateRegex); + add(NextDateRegex); + add(WeekDayRegex); + add(WeekDayOfMonthRegex); + add(SpecialDateRegex); + } + }; + + public static final Pattern OfMonth = RegExpUtility.getSafeRegExp(SpanishDateTime.OfMonthRegex); + public static final Pattern MonthEnd = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthEndRegex); + public static final Pattern WeekDayEnd = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayEnd); + public static final Pattern YearSuffix = RegExpUtility.getSafeRegExp(SpanishDateTime.YearSuffix); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MoreThanRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InConnectorRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeUnitRegex); + public static final ImmutableMap DayOfWeek = SpanishDateTime.DayOfWeek; + public static final ImmutableMap MonthOfYear = SpanishDateTime.MonthOfYear; + + public static List DateRegexList; + + public SpanishDateExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + integerExtractor = new IntegerExtractor(); // in other languages (english) has a method named get instance + ordinalExtractor = new OrdinalExtractor(); // in other languages (english) has a method named get instance + numberParser = new BaseNumberParser(new SpanishNumberParserConfiguration()); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + + DateRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor1)); + add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor2)); + add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor3)); + } + }; + + boolean enableDmy = getDmyDateFormat() || SpanishDateTime.DefaultLanguageFallback == Constants.DefaultLanguageFallback_DMY; + + if (enableDmy) { + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor5)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor8)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor9)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor4)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor6)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor7)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor10)); + } else { + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor4)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor6)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor7)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor5)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor8)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor9)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor10)); + } + } + + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + @Override + public Pattern getOfMonth() { + return OfMonth; + } + + @Override + public Pattern getMonthEnd() { + return MonthEnd; + } + + @Override + public Pattern getWeekDayEnd() { + return WeekDayEnd; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getForTheRegex() { + return ForTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return WeekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return RelativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return StrictRelativeRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return WeekDayRegex; + } + + @Override + public Pattern getPrefixArticleRegex() { + return PrefixArticleRegex; + } + + @Override + public Pattern getYearSuffix() { + return YearSuffix; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getRangeConnectorSymbolRegex() { + return RangeConnectorSymbolRegex; + } + + @Override + public Iterable getDateRegexList() { + return DateRegexList; + } + + @Override + public Iterable getImplicitDateList() { + return ImplicitDateList; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfWeek() { + return DayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return MonthOfYear; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..9af664696 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java @@ -0,0 +1,320 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDatePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDatePeriodExtractorConfiguration { + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TillRegex); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeConnectorRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthNumRegex); + public static final Pattern IllegalYearRegex = RegExpUtility.getSafeRegExp(BaseDateTime.IllegalYearRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeMonthRegex); + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthRegex); + public static final Pattern MonthSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthSuffixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + public static final Pattern PastRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PastRegex); + public static final Pattern FutureRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FutureRegex); + public static final Pattern FutureSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FutureSuffixRegex); + + // composite regexes + public static final Pattern SimpleCasesRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SimpleCasesRegex); + public static final Pattern MonthFrontSimpleCasesRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthFrontSimpleCasesRegex); + public static final Pattern MonthFrontBetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthFrontBetweenRegex); + public static final Pattern DayBetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayBetweenRegex); + public static final Pattern OneWordPeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OneWordPeriodRegex); + public static final Pattern MonthWithYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthWithYearRegex); + public static final Pattern MonthNumWithYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthNumWithYearRegex); + public static final Pattern WeekOfMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekOfMonthRegex); + public static final Pattern WeekOfYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekOfYearRegex); + public static final Pattern FollowedDateUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.FollowedDateUnit); + public static final Pattern NumberCombinedWithDateUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.NumberCombinedWithDateUnit); + public static final Pattern QuarterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.QuarterRegex); + public static final Pattern QuarterRegexYearFront = RegExpUtility.getSafeRegExp(SpanishDateTime.QuarterRegexYearFront); + public static final Pattern AllHalfYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AllHalfYearRegex); + public static final Pattern SeasonRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SeasonRegex); + public static final Pattern WhichWeekRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WhichWeekRegex); + public static final Pattern WeekOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekOfRegex); + public static final Pattern MonthOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthOfRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeUnitRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WithinNextPrefixRegex); + public static final Pattern LaterEarlyPeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LaterEarlyPeriodRegex); + public static final Pattern RestOfDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RestOfDateRegex); + public static final Pattern WeekWithWeekDayRangeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekWithWeekDayRangeRegex); + public static final Pattern YearPlusNumberRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearPlusNumberRegex); + public static final Pattern DecadeWithCenturyRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DecadeWithCenturyRegex); + public static final Pattern YearPeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearPeriodRegex); + public static final Pattern ComplexDatePeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ComplexDatePeriodRegex); + public static final Pattern RelativeDecadeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeDecadeRegex); + public static final Pattern ReferenceDatePeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ReferenceDatePeriodRegex); + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LaterRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MoreThanRegex); + public static final Pattern CenturySuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.CenturySuffixRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NowRegex); + + public static final Iterable SimpleCasesRegexes = new ArrayList() { + { + add(SimpleCasesRegex); + add(DayBetweenRegex); + add(OneWordPeriodRegex); + add(MonthWithYearRegex); + add(MonthNumWithYearRegex); + add(YearRegex); + add(YearPeriodRegex); + add(WeekOfMonthRegex); + add(WeekOfYearRegex); + add(MonthFrontBetweenRegex); + add(MonthFrontSimpleCasesRegex); + add(QuarterRegex); + add(QuarterRegexYearFront); + add(SeasonRegex); + add(RestOfDateRegex); + add(LaterEarlyPeriodRegex); + add(WeekWithWeekDayRangeRegex); + add(YearPlusNumberRegex); + add(DecadeWithCenturyRegex); + add(RelativeDecadeRegex); + add(MonthOfRegex); + } + }; + + private static final Pattern fromRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromRegex); + private static final Pattern betweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BetweenRegex); + + private final IDateTimeExtractor datePointExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IParser numberParser; + private final String[] durationDateRestrictions; + + public SpanishDatePeriodExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + + datePointExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + cardinalExtractor = CardinalExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + numberParser = new BaseNumberParser(new SpanishNumberParserConfiguration()); + + durationDateRestrictions = SpanishDateTime.DurationDateRestrictions.toArray(new String[0]); + } + + @Override + public Iterable getSimpleCasesRegexes() { + return SimpleCasesRegexes; + } + + @Override + public Pattern getIllegalYearRegex() { + return IllegalYearRegex; + } + + @Override + public Pattern getYearRegex() { + return YearRegex; + } + + @Override + public Pattern getTillRegex() { + return TillRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getFollowedDateUnit() { + return FollowedDateUnit; + } + + @Override + public Pattern getNumberCombinedWithDateUnit() { + return NumberCombinedWithDateUnit; + } + + @Override + public Pattern getPastRegex() { + return PastRegex; + } + + @Override + public Pattern getFutureRegex() { + return FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FutureSuffixRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return WeekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return MonthOfRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return YearPeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return RelativeDecadeRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return ReferenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return CenturySuffixRegex; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public String[] getDurationDateRestrictions() { + return durationDateRestrictions; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = fromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = betweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + Optional match = Arrays.stream(RegExpUtility.getMatches(RangeConnectorRegex, text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return ComplexDatePeriodRegex; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..36ba13b3c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java @@ -0,0 +1,90 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishDateTimeAltExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeAltExtractorConfiguration { + + private static final Pattern OrRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OrRegex); + private static final Pattern DayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayRegex); + + public static final Pattern ThisPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ThisPrefixRegex); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextPrefixRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); + public static final Pattern RangePrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangePrefixRegex); + + public static final Iterable RelativePrefixList = new ArrayList() { + { + add(ThisPrefixRegex); + add(PreviousPrefixRegex); + add(NextPrefixRegex); + } + }; + + public static final Iterable AmPmRegexList = new ArrayList() { + { + add(AmRegex); + add(PmRegex); + } + }; + + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor datePeriodExtractor; + + public SpanishDateTimeAltExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public Iterable getRelativePrefixList() { + return RelativePrefixList; + } + + @Override + public Iterable getAmPmRegexList() { + return AmPmRegexList; + } + + @Override + public Pattern getOrRegex() { + return OrRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return ThisPrefixRegex; + } + + @Override + public Pattern getDayRegex() { + return DayRegex; + } + + @Override public Pattern getRangePrefixRegex() { + return RangePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..f0fda81a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java @@ -0,0 +1,170 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.regex.Pattern; + +public class SpanishDateTimeExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeExtractorConfiguration { + + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrepositionRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NowRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixRegex); + + //TODO: modify it according to the corresponding English regex + + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfDayRegex); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfTodayAfterRegex); + public static final Pattern TimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfTodayBeforeRegex); + public static final Pattern SimpleTimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SimpleTimeOfTodayAfterRegex); + public static final Pattern SimpleTimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SimpleTimeOfTodayBeforeRegex); + public static final Pattern SpecificEndOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificEndOfRegex); + public static final Pattern UnspecificEndOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnspecificEndOfRegex); + + //TODO: add this for Spanish + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + public static final Pattern ConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ConnectorRegex); + public static final Pattern NumberAsTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NumberAsTimeRegex); + public static final Pattern DateNumberConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateNumberConnectorRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixAfterRegex); + + public SpanishDateTimeExtractorConfiguration(DateTimeOptions options) { + + super(options); + + integerExtractor = IntegerExtractor.getInstance(); + datePointExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + timePointExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(options)); + + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + } + + public SpanishDateTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + private IExtractor integerExtractor; + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + private IDateExtractor datePointExtractor; + + @Override + public IDateExtractor getDatePointExtractor() { + return datePointExtractor; + } + + private IDateTimeExtractor timePointExtractor; + + @Override + public IDateTimeExtractor getTimePointExtractor() { + return timePointExtractor; + } + + private IDateTimeExtractor durationExtractor; + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeUtilityConfiguration utilityConfiguration; + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getTimeOfTodayAfterRegex() { + return TimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return SimpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getTimeOfTodayBeforeRegex() { + return TimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return SimpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return SpecificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return UnspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return UnitRegex; + } + + @Override + public Pattern getNumberAsTimeRegex() { + return NumberAsTimeRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return DateNumberConnectorRegex; + } + + @Override + public Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public boolean isConnector(String text) { + + text = text.trim(); + + boolean isPreposition = Arrays.stream(RegExpUtility.getMatches(PrepositionRegex, text)).findFirst().isPresent(); + boolean isConnector = Arrays.stream(RegExpUtility.getMatches(ConnectorRegex, text)).findFirst().isPresent(); + return (StringUtility.isNullOrEmpty(text) || isPreposition || isConnector); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..8ba69bc8b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,283 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateTimePeriodExtractorConfiguration extends BaseOptionsConfiguration + implements IDateTimePeriodExtractorConfiguration { + + public static final Pattern weekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayRegex); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.DateTimePeriodNumberCombinedWithUnit); + public static final Pattern RestOfDateTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RestOfDateTimeRegex); + public static final Pattern PeriodTimeOfDayWithDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PeriodTimeOfDayWithDateRegex); + public static final Pattern RelativeTimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeTimeUnitRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.GeneralEndingRegex); + public static final Pattern MiddlePauseRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MiddlePauseRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmDescRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WithinNextPrefixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + public static final Pattern PrefixDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrefixDayRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfterRegex); + public static final Pattern FromRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromRegex); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeConnectorRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BetweenRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfDayRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeFollowedUnit); + + private final String tokenBeforeDate; + + private final IExtractor cardinalExtractor; + private final IDateTimeExtractor singleDateExtractor; + private final IDateTimeExtractor singleTimeExtractor; + private final IDateTimeExtractor singleDateTimeExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + public static final Iterable SimpleCases = new ArrayList() { + { + add(SpanishTimePeriodExtractorConfiguration.PureNumFromTo); + add(SpanishTimePeriodExtractorConfiguration.PureNumBetweenAnd); + } + }; + + public SpanishDateTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishDateTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + + cardinalExtractor = CardinalExtractor.getInstance(); + + singleDateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + singleTimeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + singleDateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(options)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateExtractor() { + return singleDateExtractor; + } + + @Override + public IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateTimeExtractor() { + return singleDateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + @Override + public Iterable getSimpleCasesRegex() { + return SimpleCases; + } + + @Override + public Pattern getPrepositionRegex() { + return SpanishDateTimeExtractorConfiguration.PrepositionRegex; + } + + @Override + public Pattern getTillRegex() { + return SpanishTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return SpanishDateTimeExtractorConfiguration.TimeOfDayRegex; + } + + @Override + public Pattern getFollowedUnit() { + return TimeFollowedUnit; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return SpanishDatePeriodExtractorConfiguration.PastRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return SpanishDatePeriodExtractorConfiguration.FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return SpanishDatePeriodExtractorConfiguration.FutureSuffixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return PrefixDayRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return PeriodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return RelativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return RestOfDateTimeRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public Pattern getMiddlePauseRegex() { + return MiddlePauseRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getBeforeRegex() { + return BeforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return AfterRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return SpanishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + Optional match = Arrays.stream(RegExpUtility.getMatches(RangeConnectorRegex, text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java new file mode 100644 index 000000000..064ff06dc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java @@ -0,0 +1,139 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishDurationExtractorConfiguration extends BaseOptionsConfiguration implements IDurationExtractorConfiguration { + + //public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnitRegex); + public static final Pattern SuffixAndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixAndRegex); + public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.FollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.DurationNumberCombinedWithUnit); + public static final Pattern AnUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AnUnitRegex); + public static final Pattern DuringRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DuringRegex); + public static final Pattern AllRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AllRegex); + public static final Pattern HalfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.HalfRegex); + public static final Pattern ConjunctionRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ConjunctionRegex); + public static final Pattern InexactNumberRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InexactNumberRegex); + public static final Pattern InexactNumberUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InexactNumberUnitRegex); + public static final Pattern RelativeDurationUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeDurationUnitRegex); + public static final Pattern DurationUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DurationUnitRegex); + public static final Pattern DurationConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DurationConnectorRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MoreThanRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanRegex); + + private final IExtractor cardinalExtractor; + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + + public SpanishDurationExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishDurationExtractorConfiguration(DateTimeOptions options) { + + super(options); + + cardinalExtractor = CardinalExtractor.getInstance(); + unitMap = SpanishDateTime.UnitMap; + unitValueMap = SpanishDateTime.UnitValueMap; + } + + @Override + public Pattern getFollowedUnit() { + return FollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return AnUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return DuringRegex; + } + + @Override + public Pattern getAllRegex() { + return AllRegex; + } + + @Override + public Pattern getHalfRegex() { + return HalfRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return SuffixAndRegex; + } + + @Override + public Pattern getConjunctionRegex() { + return ConjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return InexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return InexactNumberUnitRegex; + } + + @Override + public Pattern getRelativeDurationUnitRegex() { + return RelativeDurationUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return DurationUnitRegex; + } + + @Override + public Pattern getDurationConnectorRegex() { + return DurationConnectorRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java new file mode 100644 index 000000000..3a3e6fb8f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java @@ -0,0 +1,36 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishHolidayExtractorConfiguration extends BaseOptionsConfiguration implements IHolidayExtractorConfiguration { + + public static final Pattern H1 = RegExpUtility.getSafeRegExp(SpanishDateTime.HolidayRegex1); + + public static final Pattern H2 = RegExpUtility.getSafeRegExp(SpanishDateTime.HolidayRegex2); + + public static final Pattern H3 = RegExpUtility.getSafeRegExp(SpanishDateTime.HolidayRegex3); + + public static final Iterable HolidayRegexList = new ArrayList() { + { + add(H1); + add(H2); + add(H3); + } + }; + + public SpanishHolidayExtractorConfiguration() { + super(DateTimeOptions.None); + } + + @Override + public Iterable getHolidayRegexes() { + return HolidayRegexList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java new file mode 100644 index 000000000..db952e02b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java @@ -0,0 +1,198 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeAltExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseHolidayExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseSetExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public class SpanishMergedExtractorConfiguration extends BaseOptionsConfiguration implements IMergedExtractorConfiguration { + + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfterRegex); + public static final Pattern SinceRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SinceRegex); + public static final Pattern AroundRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AroundRegex); + public static final Pattern FromToRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromToRegex); + public static final Pattern SingleAmbiguousMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SingleAmbiguousMonthRegex); + public static final Pattern PrepositionSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrepositionSuffixRegex); + public static final Pattern AmbiguousRangeModifierPrefix = RegExpUtility.getSafeRegExp(SpanishDateTime.AmbiguousRangeModifierPrefix); + public static final Pattern NumberEndingPattern = RegExpUtility.getSafeRegExp(SpanishDateTime.NumberEndingPattern); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixAfterRegex); + public static final Pattern UnspecificDatePeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnspecificDatePeriodRegex); + public final Iterable> ambiguityFiltersDict = null; + + public static final StringMatcher SuperfluousWordMatcher = new StringMatcher(); + + public SpanishMergedExtractorConfiguration(DateTimeOptions options) { + super(options); + + setExtractor = new BaseSetExtractor(new SpanishSetExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + holidayExtractor = new BaseHolidayExtractor(new SpanishHolidayExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + dateTimeAltExtractor = new BaseDateTimeAltExtractor(new SpanishDateTimeAltExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new SpanishDateTimePeriodExtractorConfiguration(options)); + integerExtractor = IntegerExtractor.getInstance(); + } + + public final StringMatcher getSuperfluousWordMatcher() { + return SuperfluousWordMatcher; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor setExtractor; + + public final IDateTimeExtractor getSetExtractor() { + return setExtractor; + } + + private IDateTimeExtractor holidayExtractor; + + public final IDateTimeExtractor getHolidayExtractor() { + return holidayExtractor; + } + + private IDateTimeZoneExtractor timeZoneExtractor; + + public final IDateTimeZoneExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + private IDateTimeListExtractor dateTimeAltExtractor; + + public final IDateTimeListExtractor getDateTimeAltExtractor() { + return dateTimeAltExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public final Iterable> getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + @Override + public Iterable getFilterWordRegexList() { + return null; + } + + public final Pattern getAfterRegex() { + return AfterRegex; + } + + public final Pattern getBeforeRegex() { + return BeforeRegex; + } + + public final Pattern getSinceRegex() { + return SinceRegex; + } + + public final Pattern getAroundRegex() { + return AroundRegex; + } + + public final Pattern getFromToRegex() { + return FromToRegex; + } + + public final Pattern getSingleAmbiguousMonthRegex() { + return SingleAmbiguousMonthRegex; + } + + public final Pattern getPrepositionSuffixRegex() { + return PrepositionSuffixRegex; + } + + public final Pattern getAmbiguousRangeModifierPrefix() { + return null; + } + + public final Pattern getPotentialAmbiguousRangeRegex() { + return null; + } + + public final Pattern getNumberEndingPattern() { + return NumberEndingPattern; + } + + public final Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public final Pattern getUnspecificDatePeriodRegex() { + return UnspecificDatePeriodRegex; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java new file mode 100644 index 000000000..023ecb728 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java @@ -0,0 +1,119 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishSetExtractorConfiguration extends BaseOptionsConfiguration implements ISetExtractorConfiguration { + + public static final Pattern PeriodicRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PeriodicRegex); + public static final Pattern EachUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EachUnitRegex); + public static final Pattern EachPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EachPrefixRegex); + public static final Pattern EachDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EachDayRegex); + public static final Pattern BeforeEachDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BeforeEachDayRegex); + public static final Pattern SetWeekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SetWeekDayRegex); + public static final Pattern SetEachRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SetEachRegex); + + public SpanishSetExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishSetExtractorConfiguration(DateTimeOptions options) { + super(options); + + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + timeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new SpanishDateTimePeriodExtractorConfiguration(options)); + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final Pattern getLastRegex() { + return SpanishDateExtractorConfiguration.LastDateRegex; + } + + public final Pattern getEachPrefixRegex() { + return EachPrefixRegex; + } + + public final Pattern getPeriodicRegex() { + return PeriodicRegex; + } + + public final Pattern getEachUnitRegex() { + return EachUnitRegex; + } + + public final Pattern getEachDayRegex() { + return EachDayRegex; + } + + public final Pattern getBeforeEachDayRegex() { + return BeforeEachDayRegex; + } + + public final Pattern getSetWeekDayRegex() { + return SetWeekDayRegex; + } + + public final Pattern getSetEachRegex() { + return SetEachRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java new file mode 100644 index 000000000..d92bb5e73 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java @@ -0,0 +1,132 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishTimeExtractorConfiguration extends BaseOptionsConfiguration + implements ITimeExtractorConfiguration { + + // part 1: smallest component + // -------------------------------------- + public static final Pattern DescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DescRegex); + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.HourNumRegex); + public static final Pattern MinuteNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MinuteNumRegex); + + // part 2: middle level component + // -------------------------------------- + // handle "... en punto" + public static final Pattern OclockRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OclockRegex); + + // handle "... tarde" + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); + + // handle "... de la mañana" + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmRegex); + + // handle "y media ..." "menos cuarto ..." + public static final Pattern LessThanOneHour = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanOneHour); + public static final Pattern TensTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TensTimeRegex); + + // handle "seis treinta", "seis veintiuno", "seis menos diez" + public static final Pattern WrittenTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WrittenTimeRegex); + public static final Pattern TimePrefix = RegExpUtility.getSafeRegExp(SpanishDateTime.TimePrefix); + public static final Pattern TimeSuffix = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeSuffix); + public static final Pattern BasicTime = RegExpUtility.getSafeRegExp(SpanishDateTime.BasicTime); + + // part 3: regex for time + // -------------------------------------- + // handle "a las cuatro" "a las 3" + //TODO: add some new regex which have used in AtRegex + //TODO: modify according to corresponding English regex + public static final Pattern AtRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AtRegex); + public static final Pattern ConnectNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ConnectNumRegex); + public static final Pattern TimeBeforeAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeBeforeAfterRegex); + public static final Iterable TimeRegexList = new ArrayList() { + { + // (tres min pasadas las)? siete|7|(siete treinta) pm + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex1)); + + // (tres min pasadas las)? 3:00(:00)? (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex2)); + + // (tres min pasadas las)? 3.00 (pm) + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex3)); + + // (tres min pasadas las) (cinco treinta|siete|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex4)); + + // (tres min pasadas las) (cinco treinta|siete|7|7:00(:00)?) (pm)? (de la noche) + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex5)); + + // (cinco treinta|siete|7|7:00(:00)?) (pm)? (de la noche) + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex6)); + + // (En la noche) a las (cinco treinta|siete|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex7)); + + // (En la noche) (cinco treinta|siete|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex8)); + + // once (y)? veinticinco + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex9)); + + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex10)); + + // (tres menos veinte) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex11)); + + // (tres min pasadas las)? 3h00 (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex12)); + + // 340pm + add(ConnectNumRegex); + } + }; + + public final Pattern getIshRegex() { + return null; + } + + public final Iterable getTimeRegexList() { + return TimeRegexList; + } + + public final Pattern getAtRegex() { + return AtRegex; + } + + public final Pattern getTimeBeforeAfterRegex() { + return TimeBeforeAfterRegex; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor timeZoneExtractor; + + public final IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + public SpanishTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishTimeExtractorConfiguration(DateTimeOptions options) { + super(options); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..7a03533f6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java @@ -0,0 +1,154 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements ITimePeriodExtractorConfiguration { + + private String tokenBeforeDate; + + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.HourNumRegex); + public static final Pattern PureNumFromTo = RegExpUtility.getSafeRegExp(SpanishDateTime.PureNumFromTo); + public static final Pattern PureNumBetweenAnd = RegExpUtility.getSafeRegExp(SpanishDateTime.PureNumBetweenAnd); + public static final Pattern SpecificTimeFromTo = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificTimeFromTo); + public static final Pattern SpecificTimeBetweenAnd = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificTimeBetweenAnd); + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnitRegex); + public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.FollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeNumberCombinedWithUnit); + + private static final Pattern FromRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromRegex); + private static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeConnectorRegex); + private static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BetweenRegex); + + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfDayRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.GeneralEndingRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TillRegex); + + public SpanishTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + singleTimeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + integerExtractor = IntegerExtractor.getInstance(); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + } + + private IDateTimeUtilityConfiguration utilityConfiguration; + + public final IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + private IDateTimeExtractor singleTimeExtractor; + + public final IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public final IDateTimeExtractor timeZoneExtractor; + + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + + public Iterable getSimpleCasesRegex() { + return getSimpleCasesRegex; + } + + public final Iterable getSimpleCasesRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + } + }; + + public Iterable getPureNumberRegex() { + return getPureNumberRegex; + } + + public final Iterable getPureNumberRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + } + }; + + public final Pattern getTillRegex() { + return TillRegex; + } + + public final Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + public final Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + Optional match = Arrays.stream(RegExpUtility.getMatches(RangeConnectorRegex, text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..09ede1427 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java @@ -0,0 +1,44 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishTimeZoneExtractorConfiguration extends BaseOptionsConfiguration implements ITimeZoneExtractorConfiguration { + public SpanishTimeZoneExtractorConfiguration(DateTimeOptions options) { + super(options); + + } + + private Pattern directUtcRegex; + + public final Pattern getDirectUtcRegex() { + return directUtcRegex; + } + + private Pattern locationTimeSuffixRegex; + + public final Pattern getLocationTimeSuffixRegex() { + return locationTimeSuffixRegex; + } + + private StringMatcher locationMatcher; + + public final StringMatcher getLocationMatcher() { + return locationMatcher; + } + + private StringMatcher timeZoneMatcher; + + public final StringMatcher getTimeZoneMatcher() { + return timeZoneMatcher; + } + + public final ArrayList getAmbiguousTimezoneList() { + return new ArrayList<>(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java new file mode 100644 index 000000000..527a1e934 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java @@ -0,0 +1,133 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import org.javatuples.Pair; + +public class DateTimePeriodParser extends BaseDateTimePeriodParser { + + public DateTimePeriodParser(IDateTimePeriodParserConfiguration configuration) { + + super(configuration); + } + + @Override + protected DateTimeResolutionResult parseSpecificTimeOfDay(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + // handle morning, afternoon.. + MatchedTimeRangeResult matchedTimeRangeResult = this.config.getMatchedTimeRange(trimmedText, "-1", -1, -1, -1); + if (!matchedTimeRangeResult.getMatched()) { + return ret; + } + + boolean exactMatch = RegexExtension.isExactMatch(this.config.getSpecificTimeOfDayRegex(),trimmedText, true); + + if (exactMatch) { + int swift = this.config.getSwiftPrefix(trimmedText); + LocalDateTime date = referenceTime.plusDays(swift); + int day = date.getDayOfMonth(); + int month = date.getMonthValue(); + int year = date.getYear(); + + ret.setTimex(DateTimeFormatUtil.formatDate(date) + matchedTimeRangeResult.getTimeStr()); + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getEndHour(), matchedTimeRangeResult.getEndMin(), matchedTimeRangeResult.getEndMin()))); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getEndHour(), matchedTimeRangeResult.getEndMin(), matchedTimeRangeResult.getEndMin()))); + ret.setSuccess(true); + + return ret; + } + + int startIndex = trimmedText.indexOf(SpanishDateTime.Tomorrow); + if (startIndex == 0) { + startIndex = SpanishDateTime.Tomorrow.length(); + } else { + startIndex = 0; + } + + // handle Date followed by morning, afternoon + // Add handling code to handle morning, afternoon followed by Date + // Add handling code to handle early/late morning, afternoon + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config .getTimeOfDayRegex(), trimmedText.substring(startIndex))).findFirst(); + if (match.isPresent()) { + String beforeStr = trimmedText.substring(0, match.get().index).trim(); + List ers = this.config.getDateExtractor().extract(beforeStr, referenceTime); + + if (ers.size() == 0) { + return ret; + } + + DateTimeParseResult pr = this.config.getDateParser().parse(ers.get(0), referenceTime); + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + ret.setTimex(pr.getTimexStr() + matchedTimeRangeResult.getTimeStr()); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromValue( + futureDate, + futureDate.getYear(), + futureDate.getMonthValue(), + futureDate.getDayOfMonth(), + matchedTimeRangeResult.getBeginHour(), + 0, + 0), + DateUtil.safeCreateFromValue( + futureDate, + futureDate.getYear(), + futureDate.getMonthValue(), + futureDate.getDayOfMonth(), + matchedTimeRangeResult.getEndHour(), + matchedTimeRangeResult.getEndMin(), + matchedTimeRangeResult.getEndMin()))); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromValue(pastDate, + pastDate.getYear(), + pastDate.getMonthValue(), + pastDate.getDayOfMonth(), + matchedTimeRangeResult.getBeginHour(), + 0, + 0), + DateUtil.safeCreateFromValue( + pastDate, + pastDate.getYear(), + pastDate.getMonthValue(), + pastDate.getDayOfMonth(), + matchedTimeRangeResult.getEndHour(), + matchedTimeRangeResult.getEndMin(), + matchedTimeRangeResult.getEndMin()))); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..ec5a90061 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java @@ -0,0 +1,287 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.english.parsers.TimeParser; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDatePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeAltParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDurationParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.BaseDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishCommonDateTimeParserConfiguration extends BaseDateParserConfiguration implements ICommonDateTimeParserConfiguration { + + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap numbers; + private final ImmutableMap doubleNumbers; + private final ImmutableMap writtenDecades; + private final ImmutableMap specialDecadeCases; + + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + private final IDateTimeParser timeZoneParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser datePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser dateTimeAltParser; + + public SpanishCommonDateTimeParserConfiguration(DateTimeOptions options) { + + super(options); + + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + + unitMap = SpanishDateTime.UnitMap; + unitValueMap = SpanishDateTime.UnitValueMap; + seasonMap = SpanishDateTime.SeasonMap; + specialYearPrefixesMap = SpanishDateTime.SpecialYearPrefixesMap; + cardinalMap = SpanishDateTime.CardinalMap; + dayOfWeek = SpanishDateTime.DayOfWeek; + monthOfYear = SpanishDateTime.MonthOfYear; + numbers = SpanishDateTime.Numbers; + doubleNumbers = SpanishDateTime.DoubleNumbers; + writtenDecades = SpanishDateTime.WrittenDecades; + specialDecadeCases = SpanishDateTime.SpecialDecadeCases; + + cardinalExtractor = CardinalExtractor.getInstance(); + integerExtractor = IntegerExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + + numberParser = new BaseNumberParser(new SpanishNumberParserConfiguration()); + + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + dateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new SpanishDateTimePeriodExtractorConfiguration(options)); + + timeZoneParser = new BaseTimeZoneParser(); + dateParser = new BaseDateParser(new SpanishDateParserConfiguration(this)); + timeParser = new TimeParser(new SpanishTimeParserConfiguration(this)); + dateTimeParser = new BaseDateTimeParser(new SpanishDateTimeParserConfiguration(this)); + durationParser = new BaseDurationParser(new SpanishDurationParserConfiguration(this)); + datePeriodParser = new BaseDatePeriodParser(new SpanishDatePeriodParserConfiguration(this)); + timePeriodParser = new BaseTimePeriodParser(new SpanishTimePeriodParserConfiguration(this)); + dateTimePeriodParser = new BaseDateTimePeriodParser(new SpanishDateTimePeriodParserConfiguration(this)); + dateTimeAltParser = new BaseDateTimeAltParser(new SpanishDateTimeAltParserConfiguration(this)); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + @Override + public IDateTimeParser getDateTimeAltParser() { + return dateTimeAltParser; + } + + @Override public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java new file mode 100644 index 000000000..858415171 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java @@ -0,0 +1,336 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.StringExtension; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateParserConfiguration extends BaseOptionsConfiguration implements IDateParserConfiguration { + + private final String dateTokenPrefix; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IExtractor cardinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeParser durationParser; + private final ImmutableMap unitMap; + private final Iterable dateRegexes; + private final Pattern onRegex; + private final Pattern specialDayRegex; + private final Pattern specialDayWithNumRegex; + private final Pattern nextRegex; + private final Pattern thisRegex; + private final Pattern lastRegex; + private final Pattern unitRegex; + private final Pattern weekDayRegex; + private final Pattern monthRegex; + private final Pattern weekDayOfMonthRegex; + private final Pattern forTheRegex; + private final Pattern weekDayAndDayOfMonthRegex; + private final Pattern relativeMonthRegex; + private final Pattern strictRelativeRegex; + private final Pattern yearSuffix; + private final Pattern relativeWeekDayRegex; + private final Pattern relativeDayRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + + private final ImmutableMap dayOfMonth; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap cardinalMap; + private final List sameDayTerms; + private final List plusOneDayTerms; + private final List plusTwoDayTerms; + private final List minusOneDayTerms; + private final List minusTwoDayTerms; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public SpanishDateParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + dateTokenPrefix = SpanishDateTime.DateTokenPrefix; + integerExtractor = config.getIntegerExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateRegexes = Collections.unmodifiableList(SpanishDateExtractorConfiguration.DateRegexList); + onRegex = SpanishDateExtractorConfiguration.OnRegex; + specialDayRegex = SpanishDateExtractorConfiguration.SpecialDayRegex; + specialDayWithNumRegex = SpanishDateExtractorConfiguration.SpecialDayWithNumRegex; + nextRegex = SpanishDateExtractorConfiguration.NextDateRegex; + thisRegex = SpanishDateExtractorConfiguration.ThisRegex; + lastRegex = SpanishDateExtractorConfiguration.LastDateRegex; + unitRegex = SpanishDateExtractorConfiguration.DateUnitRegex; + weekDayRegex = SpanishDateExtractorConfiguration.WeekDayRegex; + monthRegex = SpanishDateExtractorConfiguration.MonthRegex; + weekDayOfMonthRegex = SpanishDateExtractorConfiguration.WeekDayOfMonthRegex; + forTheRegex = SpanishDateExtractorConfiguration.ForTheRegex; + weekDayAndDayOfMonthRegex = SpanishDateExtractorConfiguration.WeekDayAndDayOfMonthRegex; + relativeMonthRegex = SpanishDateExtractorConfiguration.RelativeMonthRegex; + strictRelativeRegex = SpanishDateExtractorConfiguration.StrictRelativeRegex; + yearSuffix = SpanishDateExtractorConfiguration.YearSuffix; + relativeWeekDayRegex = SpanishDateExtractorConfiguration.RelativeWeekDayRegex; + relativeDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeDayRegex); + nextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousPrefixRegex); + dayOfMonth = config.getDayOfMonth(); + dayOfWeek = config.getDayOfWeek(); + monthOfYear = config.getMonthOfYear(); + cardinalMap = config.getCardinalMap(); + unitMap = config.getUnitMap(); + utilityConfiguration = config.getUtilityConfiguration(); + sameDayTerms = Collections.unmodifiableList(SpanishDateTime.SameDayTerms); + plusOneDayTerms = Collections.unmodifiableList(SpanishDateTime.PlusOneDayTerms); + plusTwoDayTerms = Collections.unmodifiableList(SpanishDateTime.PlusTwoDayTerms); + minusOneDayTerms = Collections.unmodifiableList(SpanishDateTime.MinusOneDayTerms); + minusTwoDayTerms = Collections.unmodifiableList(SpanishDateTime.MinusTwoDayTerms); + } + + @Override + public String getDateTokenPrefix() { + return dateTokenPrefix; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Iterable getDateRegexes() { + return dateRegexes; + } + + @Override + public Pattern getOnRegex() { + return onRegex; + } + + @Override + public Pattern getSpecialDayRegex() { + return specialDayRegex; + } + + @Override + public Pattern getSpecialDayWithNumRegex() { + return specialDayWithNumRegex; + } + + @Override + public Pattern getNextRegex() { + return nextRegex; + } + + @Override + public Pattern getThisRegex() { + return thisRegex; + } + + @Override + public Pattern getLastRegex() { + return lastRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getMonthRegex() { + return monthRegex; + } + + @Override + public Pattern getWeekDayOfMonthRegex() { + return weekDayOfMonthRegex; + } + + @Override + public Pattern getForTheRegex() { + return forTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return weekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return relativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return strictRelativeRegex; + } + + @Override + public Pattern getYearSuffix() { + return yearSuffix; + } + + @Override + public Pattern getRelativeWeekDayRegex() { + return relativeWeekDayRegex; + } + + @Override + public Pattern getRelativeDayRegex() { + return relativeDayRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public List getSameDayTerms() { + return sameDayTerms; + } + + @Override + public List getPlusOneDayTerms() { + return plusOneDayTerms; + } + + @Override + public List getMinusOneDayTerms() { + return minusOneDayTerms; + } + + @Override + public List getPlusTwoDayTerms() { + return plusTwoDayTerms; + } + + @Override + public List getMinusTwoDayTerms() { + return minusTwoDayTerms; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Integer getSwiftMonthOrYear(String text) { + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + int swift = 0; + + Matcher regexMatcher = nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = 1; + } + + regexMatcher = previousPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = -1; + } + + return swift; + } + + @Override + public Boolean isCardinalLast(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("last"); + } + + @Override + public String normalize(String text) { + return StringExtension.normalize(text, SpanishDateTime.SpecialCharactersEquivalent); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java new file mode 100644 index 000000000..6cd0032f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java @@ -0,0 +1,621 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class SpanishDatePeriodParserConfiguration extends BaseOptionsConfiguration implements IDatePeriodParserConfiguration { + + public static final Pattern nextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextPrefixRegex); + public static final Pattern nextSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextSuffixRegex); + public static final Pattern previousPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousPrefixRegex); + public static final Pattern previousSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousSuffixRegex); + public static final Pattern thisPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ThisPrefixRegex); + public static final Pattern relativeSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeSuffixRegex); + public static final Pattern relativeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeRegex); + public static final Pattern unspecificEndOfRangeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnspecificEndOfRangeRegex); + + public SpanishDatePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + cardinalExtractor = config.getCardinalExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateParser = config.getDateParser(); + monthFrontBetweenRegex = SpanishDatePeriodExtractorConfiguration.MonthFrontBetweenRegex; + betweenRegex = SpanishDatePeriodExtractorConfiguration.DayBetweenRegex; + monthFrontSimpleCasesRegex = SpanishDatePeriodExtractorConfiguration.MonthFrontSimpleCasesRegex; + simpleCasesRegex = SpanishDatePeriodExtractorConfiguration.SimpleCasesRegex; + oneWordPeriodRegex = SpanishDatePeriodExtractorConfiguration.OneWordPeriodRegex; + monthWithYear = SpanishDatePeriodExtractorConfiguration.MonthWithYearRegex; + monthNumWithYear = SpanishDatePeriodExtractorConfiguration.MonthNumWithYearRegex; + yearRegex = SpanishDatePeriodExtractorConfiguration.YearRegex; + pastRegex = SpanishDatePeriodExtractorConfiguration.PastRegex; + futureRegex = SpanishDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = SpanishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnit = SpanishDurationExtractorConfiguration.NumberCombinedWithUnit; + weekOfMonthRegex = SpanishDatePeriodExtractorConfiguration.WeekOfMonthRegex; + weekOfYearRegex = SpanishDatePeriodExtractorConfiguration.WeekOfYearRegex; + quarterRegex = SpanishDatePeriodExtractorConfiguration.QuarterRegex; + quarterRegexYearFront = SpanishDatePeriodExtractorConfiguration.QuarterRegexYearFront; + allHalfYearRegex = SpanishDatePeriodExtractorConfiguration.AllHalfYearRegex; + seasonRegex = SpanishDatePeriodExtractorConfiguration.SeasonRegex; + whichWeekRegex = SpanishDatePeriodExtractorConfiguration.WhichWeekRegex; + weekOfRegex = SpanishDatePeriodExtractorConfiguration.WeekOfRegex; + monthOfRegex = SpanishDatePeriodExtractorConfiguration.MonthOfRegex; + restOfDateRegex = SpanishDatePeriodExtractorConfiguration.RestOfDateRegex; + laterEarlyPeriodRegex = SpanishDatePeriodExtractorConfiguration.LaterEarlyPeriodRegex; + weekWithWeekDayRangeRegex = SpanishDatePeriodExtractorConfiguration.WeekWithWeekDayRangeRegex; + yearPlusNumberRegex = SpanishDatePeriodExtractorConfiguration.YearPlusNumberRegex; + decadeWithCenturyRegex = SpanishDatePeriodExtractorConfiguration.DecadeWithCenturyRegex; + yearPeriodRegex = SpanishDatePeriodExtractorConfiguration.YearPeriodRegex; + complexDatePeriodRegex = SpanishDatePeriodExtractorConfiguration.ComplexDatePeriodRegex; + relativeDecadeRegex = SpanishDatePeriodExtractorConfiguration.RelativeDecadeRegex; + inConnectorRegex = config.getUtilityConfiguration().getInConnectorRegex(); + withinNextPrefixRegex = SpanishDatePeriodExtractorConfiguration.WithinNextPrefixRegex; + referenceDatePeriodRegex = SpanishDatePeriodExtractorConfiguration.ReferenceDatePeriodRegex; + agoRegex = SpanishDatePeriodExtractorConfiguration.AgoRegex; + laterRegex = SpanishDatePeriodExtractorConfiguration.LaterRegex; + lessThanRegex = SpanishDatePeriodExtractorConfiguration.LessThanRegex; + moreThanRegex = SpanishDatePeriodExtractorConfiguration.MoreThanRegex; + centurySuffixRegex = SpanishDatePeriodExtractorConfiguration.CenturySuffixRegex; + nowRegex = SpanishDatePeriodExtractorConfiguration.NowRegex; + + unitMap = config.getUnitMap(); + cardinalMap = config.getCardinalMap(); + dayOfMonth = config.getDayOfMonth(); + monthOfYear = config.getMonthOfYear(); + seasonMap = config.getSeasonMap(); + specialYearPrefixesMap = config.getSpecialYearPrefixesMap(); + numbers = config.getNumbers(); + writtenDecades = config.getWrittenDecades(); + specialDecadeCases = config.getSpecialDecadeCases(); + + afterNextSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfterNextSuffixRegex); + } + + // Regex + + private final String tokenBeforeDate; + + private final IDateExtractor dateExtractor; + + private final IExtractor cardinalExtractor; + + private final IExtractor ordinalExtractor; + + private final IDateTimeExtractor durationExtractor; + + private final IExtractor integerExtractor; + + private final IParser numberParser; + + private final IDateTimeParser dateParser; + + private final IDateTimeParser durationParser; + + private final Pattern monthFrontBetweenRegex; + + private final Pattern betweenRegex; + + private final Pattern monthFrontSimpleCasesRegex; + + private final Pattern simpleCasesRegex; + + private final Pattern oneWordPeriodRegex; + + private final Pattern monthWithYear; + + private final Pattern monthNumWithYear; + + private final Pattern yearRegex; + + private final Pattern pastRegex; + + private final Pattern futureRegex; + + private final Pattern futureSuffixRegex; + + private final Pattern numberCombinedWithUnit; + + private final Pattern weekOfMonthRegex; + + private final Pattern weekOfYearRegex; + + private final Pattern quarterRegex; + + private final Pattern quarterRegexYearFront; + + private final Pattern allHalfYearRegex; + + private final Pattern seasonRegex; + + private final Pattern whichWeekRegex; + + private final Pattern weekOfRegex; + + private final Pattern monthOfRegex; + + private final Pattern inConnectorRegex; + + private final Pattern withinNextPrefixRegex; + + private final Pattern restOfDateRegex; + + private final Pattern laterEarlyPeriodRegex; + + private final Pattern weekWithWeekDayRangeRegex; + + private final Pattern yearPlusNumberRegex; + + private final Pattern decadeWithCenturyRegex; + + private final Pattern yearPeriodRegex; + + private final Pattern complexDatePeriodRegex; + + private final Pattern relativeDecadeRegex; + + private final Pattern referenceDatePeriodRegex; + + private final Pattern agoRegex; + + private final Pattern laterRegex; + + private final Pattern lessThanRegex; + + private final Pattern moreThanRegex; + + private final Pattern centurySuffixRegex; + + private final Pattern afterNextSuffixRegex; + + private final Pattern nowRegex; + + // Dictionaries + private final ImmutableMap unitMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap writtenDecades; + private final ImmutableMap numbers; + private final ImmutableMap specialDecadeCases; + + @Override + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public final IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public final IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public final IParser getNumberParser() { + return numberParser; + } + + @Override + public final IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public final Pattern getMonthFrontBetweenRegex() { + return monthFrontBetweenRegex; + } + + @Override + public final Pattern getBetweenRegex() { + return betweenRegex; + } + + @Override + public final Pattern getMonthFrontSimpleCasesRegex() { + return monthFrontSimpleCasesRegex; + } + + @Override + public final Pattern getSimpleCasesRegex() { + return simpleCasesRegex; + } + + @Override + public final Pattern getOneWordPeriodRegex() { + return oneWordPeriodRegex; + } + + @Override + public final Pattern getMonthWithYear() { + return monthWithYear; + } + + @Override + public final Pattern getMonthNumWithYear() { + return monthNumWithYear; + } + + @Override + public final Pattern getYearRegex() { + return yearRegex; + } + + @Override + public final Pattern getPastRegex() { + return pastRegex; + } + + @Override + public final Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public final Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public final Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public final Pattern getWeekOfMonthRegex() { + return weekOfMonthRegex; + } + + @Override + public final Pattern getWeekOfYearRegex() { + return weekOfYearRegex; + } + + @Override + public final Pattern getQuarterRegex() { + return quarterRegex; + } + + @Override + public final Pattern getQuarterRegexYearFront() { + return quarterRegexYearFront; + } + + @Override + public final Pattern getAllHalfYearRegex() { + return allHalfYearRegex; + } + + @Override + public final Pattern getSeasonRegex() { + return seasonRegex; + } + + @Override + public final Pattern getWhichWeekRegex() { + return whichWeekRegex; + } + + @Override + public final Pattern getWeekOfRegex() { + return weekOfRegex; + } + + @Override + public final Pattern getMonthOfRegex() { + return monthOfRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return inConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public final Pattern getRestOfDateRegex() { + return restOfDateRegex; + } + + @Override + public final Pattern getLaterEarlyPeriodRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getWeekWithWeekDayRangeRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getYearPlusNumberRegex() { + return yearPlusNumberRegex; + } + + @Override + public final Pattern getDecadeWithCenturyRegex() { + return decadeWithCenturyRegex; + } + + @Override + public final Pattern getYearPeriodRegex() { + return yearPeriodRegex; + } + + @Override + public final Pattern getComplexDatePeriodRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getRelativeDecadeRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getReferenceDatePeriodRegex() { + return referenceDatePeriodRegex; + } + + @Override + public final Pattern getAgoRegex() { + return agoRegex; + } + + @Override + public final Pattern getLaterRegex() { + return laterRegex; + } + + @Override + public final Pattern getLessThanRegex() { + return lessThanRegex; + } + + @Override + public final Pattern getMoreThanRegex() { + return moreThanRegex; + } + + @Override + public final Pattern getCenturySuffixRegex() { + return centurySuffixRegex; + } + + @Override + public final Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public final Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public final Pattern getThisPrefixRegex() { + return thisPrefixRegex; + } + + @Override + public final Pattern getRelativeRegex() { + return relativeRegex; + } + + @Override + public final Pattern getUnspecificEndOfRangeRegex() { + return unspecificEndOfRangeRegex; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public int getSwiftDayOrMonth(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNextPrefix = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchNextSuffix = Arrays.stream(RegExpUtility.getMatches(nextSuffixRegex, trimmedText)).findFirst(); + Optional matchPastPrefix = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + Optional matchPastSuffix = Arrays.stream(RegExpUtility.getMatches(previousSuffixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNextPrefix.isPresent() || matchNextSuffix.isPresent()) { + swift = 1; + } else if (matchPastPrefix.isPresent() || matchPastSuffix.isPresent()) { + swift = -1; + } + + return swift; + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = -10; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNextPrefix = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchNextSuffix = Arrays.stream(RegExpUtility.getMatches(nextSuffixRegex, trimmedText)).findFirst(); + Optional matchPastPrefix = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + Optional matchPastSuffix = Arrays.stream(RegExpUtility.getMatches(previousSuffixRegex, trimmedText)).findFirst(); + Optional matchThisPresent = Arrays.stream(RegExpUtility.getMatches(thisPrefixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNextPrefix.isPresent() || matchNextSuffix.isPresent()) { + swift = 1; + } else if (matchPastPrefix.isPresent() || matchPastSuffix.isPresent()) { + swift = -1; + } else if (matchThisPresent.isPresent()) { + swift = 0; + } + + return swift; + } + + @Override + public boolean isFuture(String text) { + String trimmedText = text.trim().toLowerCase(); + + Optional matchThis = Arrays.stream(RegExpUtility.getMatches(thisPrefixRegex, trimmedText)).findFirst(); + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + return matchThis.isPresent() || matchNext.isPresent(); + } + + @Override + public boolean isLastCardinal(String text) { + String trimmedText = text.trim().toLowerCase(); + + Optional matchLast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + return matchLast.isPresent(); + } + + @Override + public boolean isMonthOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchRelative = Arrays.stream(RegExpUtility.getMatches(relativeSuffixRegex, trimmedText)).findFirst(); + return SpanishDateTime.MonthTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + SpanishDateTime.MonthTerms.stream().anyMatch(o -> trimmedText.contains(o)) && matchRelative.isPresent(); + } + + @Override + public boolean isMonthToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + return SpanishDateTime.MonthToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isWeekend(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchRelative = Arrays.stream(RegExpUtility.getMatches(relativeSuffixRegex, trimmedText)).findFirst(); + return SpanishDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + SpanishDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.contains(o)) && matchRelative.isPresent(); + } + + @Override + public boolean isWeekOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchRelative = Arrays.stream(RegExpUtility.getMatches(relativeSuffixRegex, trimmedText)).findFirst(); + return (SpanishDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + SpanishDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.contains(o)) && matchRelative.isPresent()) && + !SpanishDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + return SpanishDateTime.YearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + + return SpanishDateTime.YearToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..38020f75f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; + +public class SpanishDateTimeAltParserConfiguration implements IDateTimeAltParserConfiguration { + + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser datePeriodParser; + + public SpanishDateTimeAltParserConfiguration(ICommonDateTimeParserConfiguration config) { + dateTimeParser = config.getDateTimeParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + datePeriodParser = config.getDatePeriodParser(); + } + + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public IDateTimeParser getDateParser() { + return dateParser; + } + + public IDateTimeParser getTimeParser() { + return timeParser; + } + + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java new file mode 100644 index 000000000..8ad340630 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java @@ -0,0 +1,237 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateTimeParserConfiguration extends BaseOptionsConfiguration implements IDateTimeParserConfiguration { + + public final String tokenBeforeDate; + public final String tokenBeforeTime; + + public final IDateTimeExtractor dateExtractor; + public final IDateTimeExtractor timeExtractor; + public final IDateTimeParser dateParser; + public final IDateTimeParser timeParser; + public final IExtractor cardinalExtractor; + public final IExtractor integerExtractor; + public final IParser numberParser; + public final IDateTimeExtractor durationExtractor; + public final IDateTimeParser durationParser; + + public final ImmutableMap unitMap; + public final ImmutableMap numbers; + + public final Pattern nowRegex; + public final Pattern amTimeRegex; + public final Pattern pmTimeRegex; + public final Pattern simpleTimeOfTodayAfterRegex; + public final Pattern simpleTimeOfTodayBeforeRegex; + public final Pattern specificTimeOfDayRegex; + public final Pattern specificEndOfRegex; + public final Pattern unspecificEndOfRegex; + public final Pattern unitRegex; + public final Pattern dateNumberConnectorRegex; + + public final IDateTimeUtilityConfiguration utilityConfiguration; + + public SpanishDateTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + durationParser = config.getDurationParser(); + integerExtractor = config.getIntegerExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + utilityConfiguration = config.getUtilityConfiguration(); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + tokenBeforeTime = SpanishDateTime.TokenBeforeTime; + + nowRegex = SpanishDateTimeExtractorConfiguration.NowRegex; + unitRegex = SpanishDateTimeExtractorConfiguration.UnitRegex; + specificEndOfRegex = SpanishDateTimeExtractorConfiguration.SpecificEndOfRegex; + unspecificEndOfRegex = SpanishDateTimeExtractorConfiguration.UnspecificEndOfRegex; + specificTimeOfDayRegex = SpanishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + dateNumberConnectorRegex = SpanishDateTimeExtractorConfiguration.DateNumberConnectorRegex; + simpleTimeOfTodayAfterRegex = SpanishDateTimeExtractorConfiguration.SimpleTimeOfTodayAfterRegex; + simpleTimeOfTodayBeforeRegex = SpanishDateTimeExtractorConfiguration.SimpleTimeOfTodayBeforeRegex; + + pmTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); + amTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmTimeRegex); + } + + @Override + public int getHour(String text, int hour) { + + String trimmedText = text.trim().toLowerCase(); + int result = hour; + + //TODO: Replace with a regex + if ((trimmedText.endsWith("mañana") || trimmedText.endsWith("madrugada")) && hour >= Constants.HalfDayHourCount) { + result -= Constants.HalfDayHourCount; + } else if (!(trimmedText.endsWith("mañana") || trimmedText.endsWith("madrugada")) && hour < Constants.HalfDayHourCount) { + result += Constants.HalfDayHourCount; + } + + return result; + } + + @Override + public ResultTimex getMatchedNowTimex(String text) { + + String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.endsWith("ahora") || trimmedText.endsWith("mismo") || trimmedText.endsWith("momento")) { + return new ResultTimex(true, "PRESENT_REF"); + } else if (trimmedText.endsWith("posible") || trimmedText.endsWith("pueda") || trimmedText.endsWith("puedas") || + trimmedText.endsWith("podamos") || trimmedText.endsWith("puedan")) { + return new ResultTimex(true, "FUTURE_REF"); + } else if (trimmedText.endsWith("mente")) { + return new ResultTimex(true, "PAST_REF"); + } + + return new ResultTimex(false, null); + } + + @Override + public int getSwiftDay(String text) { + + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + Matcher regexMatcher = SpanishDatePeriodParserConfiguration.previousPrefixRegex.matcher(trimmedText); + + int swift = 0; + if (regexMatcher.find()) { + swift = 1; + } else { + regexMatcher = SpanishDatePeriodParserConfiguration.nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = -1; + } + } + + return swift; + } + + @Override + public boolean containsAmbiguousToken(String text, String matchedText) { + return text.contains("esta mañana") && matchedText.contains("mañana"); + } + + @Override public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override public String getTokenBeforeTime() { + return tokenBeforeTime; + } + + @Override public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override public IParser getNumberParser() { + return numberParser; + } + + @Override public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override public ImmutableMap getNumbers() { + return numbers; + } + + @Override public Pattern getNowRegex() { + return nowRegex; + } + + public Pattern getAMTimeRegex() { + return amTimeRegex; + } + + public Pattern getPMTimeRegex() { + return pmTimeRegex; + } + + @Override public Pattern getSimpleTimeOfTodayAfterRegex() { + return simpleTimeOfTodayAfterRegex; + } + + @Override public Pattern getSimpleTimeOfTodayBeforeRegex() { + return simpleTimeOfTodayBeforeRegex; + } + + @Override public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override public Pattern getSpecificEndOfRegex() { + return specificEndOfRegex; + } + + @Override public Pattern getUnspecificEndOfRegex() { + return unspecificEndOfRegex; + } + + @Override public Pattern getUnitRegex() { + return unitRegex; + } + + @Override public Pattern getDateNumberConnectorRegex() { + return dateNumberConnectorRegex; + } + + @Override public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..4626f201a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java @@ -0,0 +1,347 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateTimePeriodParserConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodParserConfiguration { + + private final String tokenBeforeDate; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor cardinalExtractor; + + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern timeOfDayRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnitRegex; + private final Pattern unitRegex; + private final Pattern periodTimeOfDayWithDateRegex; + private final Pattern relativeTimeUnitRegex; + private final Pattern restOfDateTimeRegex; + private final Pattern amDescRegex; + private final Pattern pmDescRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern prefixDayRegex; + private final Pattern beforeRegex; + private final Pattern afterRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + + /*public static final Pattern MorningStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MorningStartEndRegex); + public static final Pattern AfternoonStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfternoonStartEndRegex); + public static final Pattern EveningStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EveningStartEndRegex); + public static final Pattern NightStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NightStartEndRegex);*/ + + public SpanishDateTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + numberParser = config.getNumberParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + timePeriodParser = config.getTimePeriodParser(); + durationParser = config.getDurationParser(); + dateTimeParser = config.getDateTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + + pureNumberFromToRegex = SpanishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = SpanishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeOfDayRegex = SpanishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + timeOfDayRegex = SpanishDateTimeExtractorConfiguration.TimeOfDayRegex; + pastRegex = SpanishDatePeriodExtractorConfiguration.PastRegex; + futureRegex = SpanishDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = SpanishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnitRegex = SpanishDateTimePeriodExtractorConfiguration.NumberCombinedWithUnit; + unitRegex = SpanishTimePeriodExtractorConfiguration.UnitRegex; + periodTimeOfDayWithDateRegex = SpanishDateTimePeriodExtractorConfiguration.PeriodTimeOfDayWithDateRegex; + relativeTimeUnitRegex = SpanishDateTimePeriodExtractorConfiguration.RelativeTimeUnitRegex; + restOfDateTimeRegex = SpanishDateTimePeriodExtractorConfiguration.RestOfDateTimeRegex; + amDescRegex = SpanishDateTimePeriodExtractorConfiguration.AmDescRegex; + pmDescRegex = SpanishDateTimePeriodExtractorConfiguration.PmDescRegex; + withinNextPrefixRegex = SpanishDateTimePeriodExtractorConfiguration.WithinNextPrefixRegex; + prefixDayRegex = SpanishDateTimePeriodExtractorConfiguration.PrefixDayRegex; + beforeRegex = SpanishDateTimePeriodExtractorConfiguration.BeforeRegex; + afterRegex = SpanishDateTimePeriodExtractorConfiguration.AfterRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnitRegex() { + return numberCombinedWithUnitRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return periodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return relativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return restOfDateTimeRegex; + } + + @Override + public Pattern getAmDescRegex() { + return amDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return pmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return prefixDayRegex; + } + + @Override + public Pattern getBeforeRegex() { + return beforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return afterRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public MatchedTimeRangeResult getMatchedTimeRange(String text, String timeStr, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + beginHour = 0; + endHour = 0; + endMin = 0; + boolean result = false; + + // TODO: modify it according to the coresponding function in English part + if (trimmedText.endsWith("madrugada")) { + timeStr = "TDA"; + beginHour = 4; + endHour = 8; + result = true; + } else if (trimmedText.endsWith("mañana")) { + timeStr = "TMO"; + beginHour = 8; + endHour = Constants.HalfDayHourCount; + result = true; + } else if (trimmedText.contains("pasado mediodia") || trimmedText.contains("pasado el mediodia")) { + timeStr = "TAF"; + beginHour = Constants.HalfDayHourCount; + endHour = 16; + result = true; + } else if (trimmedText.endsWith("tarde")) { + timeStr = "TEV"; + beginHour = 16; + endHour = 20; + result = true; + } else if (trimmedText.endsWith("noche")) { + timeStr = "TNI"; + beginHour = 20; + endHour = 23; + endMin = 59; + result = true; + } else { + timeStr = null; + } + + return new MatchedTimeRangeResult(result, timeStr, beginHour, endHour, endMin); + } + + @Override + public int getSwiftPrefix(String text) { + + String trimmedText = text.trim().toLowerCase(); + + Pattern regex = Pattern.compile(SpanishDateTime.PreviousPrefixRegex); + Matcher regexMatcher = regex.matcher(trimmedText); + + int swift = 0; + if (regexMatcher.find() || trimmedText.equals("anoche")) { + swift = -1; + } else { + regex = Pattern.compile(SpanishDateTime.NextPrefixRegex); + regexMatcher = regex.matcher(text); + if (regexMatcher.find()) { + swift = 1; + } + } + + return swift; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java new file mode 100644 index 000000000..0c7cd1487 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java @@ -0,0 +1,145 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDurationExtractorConfiguration; + +import java.util.regex.Pattern; + +public class SpanishDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { + + private final IExtractor cardinalExtractor; + private final IExtractor durationExtractor; + private final IParser numberParser; + + private final Pattern numberCombinedWithUnit; + private final Pattern anUnitRegex; + private final Pattern duringRegex; + private final Pattern allDateUnitRegex; + private final Pattern halfDateUnitRegex; + private final Pattern suffixAndRegex; + private final Pattern followedUnit; + private final Pattern conjunctionRegex; + private final Pattern inexactNumberRegex; + private final Pattern inexactNumberUnitRegex; + private final Pattern durationUnitRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap doubleNumbers; + + public SpanishDurationParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(), false); + numberCombinedWithUnit = SpanishDurationExtractorConfiguration.NumberCombinedWithUnit; + + anUnitRegex = SpanishDurationExtractorConfiguration.AnUnitRegex; + duringRegex = SpanishDurationExtractorConfiguration.DuringRegex; + allDateUnitRegex = SpanishDurationExtractorConfiguration.AllRegex; + halfDateUnitRegex = SpanishDurationExtractorConfiguration.HalfRegex; + suffixAndRegex = SpanishDurationExtractorConfiguration.SuffixAndRegex; + followedUnit = SpanishDurationExtractorConfiguration.FollowedUnit; + conjunctionRegex = SpanishDurationExtractorConfiguration.ConjunctionRegex; + inexactNumberRegex = SpanishDurationExtractorConfiguration.InexactNumberRegex; + inexactNumberUnitRegex = SpanishDurationExtractorConfiguration.InexactNumberUnitRegex; + durationUnitRegex = SpanishDurationExtractorConfiguration.DurationUnitRegex; + + unitMap = config.getUnitMap(); + unitValueMap = config.getUnitValueMap(); + doubleNumbers = config.getDoubleNumbers(); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return anUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return duringRegex; + } + + @Override + public Pattern getAllDateUnitRegex() { + return allDateUnitRegex; + } + + @Override + public Pattern getHalfDateUnitRegex() { + return halfDateUnitRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return suffixAndRegex; + } + + @Override + public Pattern getFollowedUnit() { + return followedUnit; + } + + @Override + public Pattern getConjunctionRegex() { + return conjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return inexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return inexactNumberUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return durationUnitRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java new file mode 100644 index 000000000..0eab930ff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java @@ -0,0 +1,137 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.HolidayFunctions; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.IntFunction; + +public class SpanishHolidayParserConfiguration extends BaseHolidayParserConfiguration { + + public SpanishHolidayParserConfiguration() { + + super(); + + this.setHolidayRegexList(SpanishHolidayExtractorConfiguration.HolidayRegexList); + + HashMap> holidayNamesMap = new HashMap<>(); + for (Map.Entry entry : SpanishDateTime.HolidayNames.entrySet()) { + if (entry.getValue() instanceof String[]) { + holidayNamesMap.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + this.setHolidayNames(ImmutableMap.copyOf(holidayNamesMap)); + + HashMap variableHolidaysTimexMap = new HashMap<>(); + for (Map.Entry entry : SpanishDateTime.VariableHolidaysTimexDictionary.entrySet()) { + if (entry.getValue() instanceof String) { + variableHolidaysTimexMap.put(entry.getKey(), entry.getValue()); + } + } + this.setVariableHolidaysTimexDictionary(ImmutableMap.copyOf(variableHolidaysTimexMap)); + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = StringUtility + .trimStart(StringUtility.trimEnd(text)).toLowerCase(Locale.ROOT); + int swift = -10; + Optional matchNextPrefixRegex = Arrays.stream(RegExpUtility.getMatches( + SpanishDatePeriodParserConfiguration.nextPrefixRegex, text)).findFirst(); + Optional matchPastPrefixRegex = Arrays.stream(RegExpUtility.getMatches( + SpanishDatePeriodParserConfiguration.previousPrefixRegex, text)).findFirst(); + Optional matchThisPrefixRegex = Arrays.stream(RegExpUtility.getMatches( + SpanishDatePeriodParserConfiguration.thisPrefixRegex, text)).findFirst(); + if (matchNextPrefixRegex.isPresent() && matchNextPrefixRegex.get().length == text.trim().length()) { + swift = 1; + } else if (matchPastPrefixRegex.isPresent() && matchPastPrefixRegex.get().length == text.trim().length()) { + swift = -1; + } else if (matchThisPrefixRegex.isPresent() && matchThisPrefixRegex.get().length == text.trim().length()) { + swift = 0; + } + + return swift; + } + + public String sanitizeHolidayToken(String holiday) { + return holiday.replace(" ", "") + .replace("á", "a") + .replace("é", "e") + .replace("í", "i") + .replace("ó", "o") + .replace("ú", "u"); + } + + @Override + protected HashMap> initHolidayFuncs() { + + HashMap> holidays = new HashMap<>(super.initHolidayFuncs()); + holidays.put("padres", SpanishHolidayParserConfiguration::fathersDay); + holidays.put("madres", SpanishHolidayParserConfiguration::mothersDay); + holidays.put("acciondegracias", SpanishHolidayParserConfiguration::thanksgivingDay); + holidays.put("trabajador", SpanishHolidayParserConfiguration::internationalWorkersDay); + holidays.put("delaraza", SpanishHolidayParserConfiguration::columbusDay); + holidays.put("memoria", SpanishHolidayParserConfiguration::memorialDay); + holidays.put("pascuas", SpanishHolidayParserConfiguration::pascuas); + holidays.put("navidad", SpanishHolidayParserConfiguration::christmasDay); + holidays.put("nochebuena", SpanishHolidayParserConfiguration::christmasEve); + holidays.put("añonuevo", SpanishHolidayParserConfiguration::newYear); + holidays.put("nochevieja", SpanishHolidayParserConfiguration::newYearEve); + holidays.put("yuandan", SpanishHolidayParserConfiguration::newYear); + holidays.put("maestro", SpanishHolidayParserConfiguration::teacherDay); + holidays.put("todoslossantos", SpanishHolidayParserConfiguration::halloweenDay); + holidays.put("niño", SpanishHolidayParserConfiguration::childrenDay); + holidays.put("mujer", SpanishHolidayParserConfiguration::femaleDay); + + return holidays; + } + + private static LocalDateTime newYear(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 1); + } + + private static LocalDateTime newYearEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 31); + } + + private static LocalDateTime christmasDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 25); + } + + private static LocalDateTime christmasEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 24); + } + + private static LocalDateTime femaleDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 8); + } + + private static LocalDateTime childrenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 1); + } + + private static LocalDateTime halloweenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 10, 31); + } + + private static LocalDateTime teacherDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 9, 10); + } + + private static LocalDateTime pascuas(int year) { + return HolidayFunctions.calculateHolidayByEaster(year); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java new file mode 100644 index 000000000..3c5ea9e4e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java @@ -0,0 +1,76 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseSetParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +public class SpanishMergedParserConfiguration extends SpanishCommonDateTimeParserConfiguration implements IMergedParserConfiguration { + + public SpanishMergedParserConfiguration(DateTimeOptions options) { + super(options); + + beforeRegex = SpanishMergedExtractorConfiguration.BeforeRegex; + afterRegex = SpanishMergedExtractorConfiguration.AfterRegex; + sinceRegex = SpanishMergedExtractorConfiguration.SinceRegex; + aroundRegex = SpanishMergedExtractorConfiguration.AroundRegex; + suffixAfterRegex = SpanishMergedExtractorConfiguration.SuffixAfterRegex; + yearRegex = SpanishDatePeriodExtractorConfiguration.YearRegex; + superfluousWordMatcher = SpanishMergedExtractorConfiguration.SuperfluousWordMatcher; + + getParser = new BaseSetParser(new SpanishSetParserConfiguration(this)); + holidayParser = new BaseHolidayParser(new SpanishHolidayParserConfiguration()); + } + + private final Pattern beforeRegex; + private final Pattern afterRegex; + private final Pattern sinceRegex; + private final Pattern aroundRegex; + private final Pattern suffixAfterRegex; + private final Pattern yearRegex; + private final IDateTimeParser getParser; + private final IDateTimeParser holidayParser; + private final StringMatcher superfluousWordMatcher; + + public Pattern getBeforeRegex() { + return beforeRegex; + } + + public Pattern getAfterRegex() { + return afterRegex; + } + + public Pattern getSinceRegex() { + return sinceRegex; + } + + public Pattern getAroundRegex() { + return aroundRegex; + } + + public Pattern getSuffixAfterRegex() { + return suffixAfterRegex; + } + + public Pattern getYearRegex() { + return yearRegex; + } + + public IDateTimeParser getGetParser() { + return getParser; + } + + public IDateTimeParser getHolidayParser() { + return holidayParser; + } + + public StringMatcher getSuperfluousWordMatcher() { + return superfluousWordMatcher; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java new file mode 100644 index 000000000..9cf688cb2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java @@ -0,0 +1,218 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishSetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; + +import java.util.Locale; +import java.util.regex.Pattern; + +public class SpanishSetParserConfiguration extends BaseOptionsConfiguration implements ISetParserConfiguration { + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeParser durationParser; + + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateTimeParser timeParser; + + public final IDateTimeParser getTimeParser() { + return timeParser; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeParser dateParser; + + public final IDateTimeParser getDateParser() { + return dateParser; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeParser dateTimeParser; + + public final IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeParser datePeriodParser; + + public final IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeParser timePeriodParser; + + public final IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + private IDateTimeParser dateTimePeriodParser; + + public final IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + private ImmutableMap unitMap; + + public final ImmutableMap getUnitMap() { + return unitMap; + } + + private Pattern eachPrefixRegex; + + public final Pattern getEachPrefixRegex() { + return eachPrefixRegex; + } + + private Pattern periodicRegex; + + public final Pattern getPeriodicRegex() { + return periodicRegex; + } + + private Pattern eachUnitRegex; + + public final Pattern getEachUnitRegex() { + return eachUnitRegex; + } + + private Pattern eachDayRegex; + + public final Pattern getEachDayRegex() { + return eachDayRegex; + } + + private Pattern setWeekDayRegex; + + public final Pattern getSetWeekDayRegex() { + return setWeekDayRegex; + } + + private Pattern setEachRegex; + + public final Pattern getSetEachRegex() { + return setEachRegex; + } + + public SpanishSetParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + durationExtractor = config.getDurationExtractor(); + timeExtractor = config.getTimeExtractor(); + dateExtractor = config.getDateExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + datePeriodExtractor = config.getDatePeriodExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + dateTimePeriodExtractor = config.getDateTimePeriodExtractor(); + + durationParser = config.getDurationParser(); + timeParser = config.getTimeParser(); + dateParser = config.getDateParser(); + dateTimeParser = config.getDateTimeParser(); + datePeriodParser = config.getDatePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + unitMap = config.getUnitMap(); + + eachPrefixRegex = SpanishSetExtractorConfiguration.EachPrefixRegex; + periodicRegex = SpanishSetExtractorConfiguration.PeriodicRegex; + eachUnitRegex = SpanishSetExtractorConfiguration.EachUnitRegex; + eachDayRegex = SpanishSetExtractorConfiguration.EachDayRegex; + setWeekDayRegex = SpanishSetExtractorConfiguration.SetWeekDayRegex; + setEachRegex = SpanishSetExtractorConfiguration.SetEachRegex; + } + + public MatchedTimexResult getMatchedDailyTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + if (trimmedText.endsWith("diario") || trimmedText.endsWith("diariamente")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("semanalmente")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("quincenalmente")) { + result.setTimex("P2W"); + } else if (trimmedText.equals("mensualmente")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("anualmente")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } + + public MatchedTimexResult getMatchedUnitTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + if (trimmedText.equals("día") || trimmedText.equals("dia") || trimmedText.equals("días") || trimmedText.equals("dias")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("semana") || trimmedText.equals("semanas")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("mes") || trimmedText.equals("meses")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("año") || trimmedText.equals("años")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java new file mode 100644 index 000000000..31e7b56ad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java @@ -0,0 +1,161 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class SpanishTimeParserConfiguration extends BaseOptionsConfiguration implements ITimeParserConfiguration { + + public String timeTokenPrefix = SpanishDateTime.TimeTokenPrefix; + + public final Pattern atRegex; + public Pattern mealTimeRegex; + + private final Iterable timeRegexes; + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeParser timeZoneParser; + + public SpanishTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + timeZoneParser = new BaseTimeZoneParser(); + + atRegex = SpanishTimeExtractorConfiguration.AtRegex; + timeRegexes = SpanishTimeExtractorConfiguration.TimeRegexList; + } + + @Override + public String getTimeTokenPrefix() { + return timeTokenPrefix; + } + + @Override + public Pattern getAtRegex() { + return atRegex; + } + + @Override + public Iterable getTimeRegexes() { + return timeRegexes; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public PrefixAdjustResult adjustByPrefix(String prefix, int hour, int min, boolean hasMin) { + + int deltaMin = 0; + String trimmedPrefix = prefix.trim().toLowerCase(); + + if (trimmedPrefix.startsWith("cuarto") || trimmedPrefix.startsWith("y cuarto")) { + deltaMin = 15; + } else if (trimmedPrefix.startsWith("menos cuarto")) { + deltaMin = -15; + } else if (trimmedPrefix.startsWith("media") || trimmedPrefix.startsWith("y media")) { + deltaMin = 30; + } else { + Optional match = Arrays.stream(RegExpUtility.getMatches(SpanishTimeExtractorConfiguration.LessThanOneHour, trimmedPrefix)).findFirst(); + if (match.isPresent()) { + String minStr = match.get().getGroup("deltamin").value; + if (!StringUtility.isNullOrWhiteSpace(minStr)) { + deltaMin = Integer.parseInt(minStr); + } else { + minStr = match.get().getGroup("deltaminnum").value.toLowerCase(); + deltaMin = numbers.getOrDefault(minStr, 0); + } + } + } + + if (trimmedPrefix.endsWith("pasadas") || trimmedPrefix.endsWith("pasados") || trimmedPrefix.endsWith("pasadas las") || + trimmedPrefix.endsWith("pasados las") || trimmedPrefix.endsWith("pasadas de las") || trimmedPrefix.endsWith("pasados de las")) { + //deltaMin is positive + } else if (trimmedPrefix.endsWith("para la") || trimmedPrefix.endsWith("para las") || + trimmedPrefix.endsWith("antes de la") || trimmedPrefix.endsWith("antes de las")) { + deltaMin = -deltaMin; + } + + min += deltaMin; + if (min < 0) { + min += 60; + hour -= 1; + } + + hasMin = hasMin || (min != 0); + + return new PrefixAdjustResult(hour, min, hasMin); + } + + @Override + public SuffixAdjustResult adjustBySuffix(String suffix, int hour, int min, boolean hasMin, boolean hasAm, boolean hasPm) { + + String trimmedSuffix = suffix.trim().toLowerCase(); + PrefixAdjustResult prefixAdjustResult = adjustByPrefix(trimmedSuffix,hour, min, hasMin); + hour = prefixAdjustResult.hour; + min = prefixAdjustResult.minute; + hasMin = prefixAdjustResult.hasMin; + + int deltaHour = 0; + ConditionalMatch match = RegexExtension.matchExact(SpanishTimeExtractorConfiguration.TimeSuffix, trimmedSuffix, true); + if (match.getSuccess()) { + + String oclockStr = match.getMatch().get().getGroup("oclock").value; + if (StringUtility.isNullOrEmpty(oclockStr)) { + + String amStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + if (!StringUtility.isNullOrEmpty(amStr)) { + if (hour >= Constants.HalfDayHourCount) { + deltaHour = -Constants.HalfDayHourCount; + } + hasAm = true; + } + + String pmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + if (!StringUtility.isNullOrEmpty(pmStr)) { + if (hour < Constants.HalfDayHourCount) { + deltaHour = Constants.HalfDayHourCount; + } + hasPm = true; + } + } + } + + hour = (hour + deltaHour) % 24; + + return new SuffixAdjustResult(hour, min, hasMin, hasAm, hasPm); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java new file mode 100644 index 000000000..c4e8debcc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java @@ -0,0 +1,152 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.TimeOfDayResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; + +import java.util.regex.Pattern; + +public class SpanishTimePeriodParserConfiguration extends BaseOptionsConfiguration implements ITimePeriodParserConfiguration { + + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IExtractor integerExtractor; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeFromToRegex; + private final Pattern specificTimeBetweenAndRegex; + private final Pattern timeOfDayRegex; + private final Pattern generalEndingRegex; + private final Pattern tillRegex; + + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public SpanishTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + integerExtractor = config.getIntegerExtractor(); + timeParser = config.getTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + pureNumberFromToRegex = SpanishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = SpanishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeFromToRegex = SpanishTimePeriodExtractorConfiguration.SpecificTimeFromTo; + specificTimeBetweenAndRegex = SpanishTimePeriodExtractorConfiguration.SpecificTimeBetweenAnd; + timeOfDayRegex = SpanishTimePeriodExtractorConfiguration.TimeOfDayRegex; + generalEndingRegex = SpanishTimePeriodExtractorConfiguration.GeneralEndingRegex; + tillRegex = SpanishTimePeriodExtractorConfiguration.TillRegex; + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeFromToRegex() { + return specificTimeFromToRegex; + } + + @Override + public Pattern getSpecificTimeBetweenAndRegex() { + return specificTimeBetweenAndRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return generalEndingRegex; + } + + @Override + public Pattern getTillRegex() { + return tillRegex; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public MatchedTimeRangeResult getMatchedTimexRange(String text, String timex, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(); + + beginHour = 0; + endHour = 0; + endMin = 0; + + String timeOfDay = ""; + + if (SpanishDateTime.EarlyMorningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.EarlyMorning; + } else if (SpanishDateTime.MorningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Morning; + } else if (SpanishDateTime.AfternoonTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Afternoon; + } else if (SpanishDateTime.EveningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Evening; + } else if (SpanishDateTime.NightTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Night; + } else { + timex = null; + return new MatchedTimeRangeResult(false, timex, beginHour, endHour, endMin); + } + + TimeOfDayResolutionResult result = TimexUtility.parseTimeOfDay(timeOfDay); + + return new MatchedTimeRangeResult(true, result.getTimex(), result.getBeginHour(), result.getEndHour(), result.getEndMin()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java new file mode 100644 index 000000000..874d5e904 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java @@ -0,0 +1,86 @@ +package com.microsoft.recognizers.text.datetime.spanish.utilities; + +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AgoRegex); + + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LaterRegex); + + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InConnectorRegex); + + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WithinNextPrefixRegex); + + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmDescRegex); + + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmDescRegex); + + public static final Pattern AmPmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmPmDescRegex); + + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeUnitRegex); + + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + + public static final Pattern CommonDatePrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.CommonDatePrefixRegex); + + @Override + public final Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public final Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public final Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public final Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public final Pattern getAmPmDescRegex() { + return AmPmDescRegex; + } + + @Override + public final Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public final Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public final Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public final Pattern getCommonDatePrefixRegex() { + return CommonDatePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java new file mode 100644 index 000000000..2cff56be6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java @@ -0,0 +1,197 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; + +public class AgoLaterUtil { + public static DateTimeResolutionResult parseDurationWithAgoAndLater(String text, LocalDateTime referenceTime, + IDateTimeExtractor durationExtractor, IDateTimeParser durationParser, ImmutableMap unitMap, + Pattern unitRegex, IDateTimeUtilityConfiguration utilityConfiguration, + Function getSwiftDay) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List durationRes = durationExtractor.extract(text, referenceTime); + if (durationRes.size() > 0) { + DateTimeParseResult pr = durationParser.parse(durationRes.get(0), referenceTime); + Match[] matches = RegExpUtility.getMatches(unitRegex, text); + if (matches.length > 0) { + String afterStr = text.substring(durationRes.get(0).getStart() + durationRes.get(0).getLength()).trim() + .toLowerCase(); + + String beforeStr = text.substring(0, durationRes.get(0).getStart()).trim().toLowerCase(); + + AgoLaterMode mode = AgoLaterMode.DATE; + if (pr.getTimexStr().contains("T")) { + mode = AgoLaterMode.DATETIME; + } + + if (pr.getValue() != null) { + return getAgoLaterResult(pr, afterStr, beforeStr, referenceTime, utilityConfiguration, mode, + getSwiftDay); + } + } + } + return ret; + } + + private static DateTimeResolutionResult getAgoLaterResult(DateTimeParseResult durationParseResult, String afterStr, + String beforeStr, LocalDateTime referenceTime, IDateTimeUtilityConfiguration utilityConfiguration, + AgoLaterMode mode, Function getSwiftDay) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + LocalDateTime resultDateTime = referenceTime; + String timex = durationParseResult.getTimexStr(); + + if (((DateTimeResolutionResult)durationParseResult.getValue()).getMod() == Constants.MORE_THAN_MOD) { + ret.setMod(Constants.MORE_THAN_MOD); + } else if (((DateTimeResolutionResult)durationParseResult.getValue()).getMod() == Constants.LESS_THAN_MOD) { + ret.setMod(Constants.LESS_THAN_MOD); + } + + if (MatchingUtil.containsAgoLaterIndex(afterStr, utilityConfiguration.getAgoRegex())) { + Optional match = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getAgoRegex(), afterStr)).findFirst(); + int swift = 0; + + // Handle cases like "3 days before yesterday" + if (match.isPresent() && !StringUtility.isNullOrEmpty(match.get().getGroup("day").value)) { + swift = getSwiftDay.apply(match.get().getGroup("day").value); + } + + resultDateTime = DurationParsingUtil.shiftDateTime(timex, referenceTime.plusDays(swift), false); + + ((DateTimeResolutionResult)durationParseResult.getValue()).setMod(Constants.BEFORE_MOD); + } else if (MatchingUtil.containsAgoLaterIndex(afterStr, utilityConfiguration.getLaterRegex()) || + MatchingUtil.containsTermIndex(beforeStr, utilityConfiguration.getInConnectorRegex())) { + Optional match = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getLaterRegex(), afterStr)).findFirst(); + int swift = 0; + + // Handle cases like "3 days after tomorrow" + if (match.isPresent() && !StringUtility.isNullOrEmpty(match.get().getGroup("day").value)) { + swift = getSwiftDay.apply(match.get().getGroup("day").value); + } + + resultDateTime = DurationParsingUtil.shiftDateTime(timex, referenceTime.plusDays(swift), true); + + ((DateTimeResolutionResult)durationParseResult.getValue()).setMod(Constants.AFTER_MOD); + } + + if (resultDateTime != referenceTime) { + if (mode.equals(AgoLaterMode.DATE)) { + ret.setTimex(DateTimeFormatUtil.luisDate(resultDateTime)); + } else if (mode.equals(AgoLaterMode.DATETIME)) { + ret.setTimex(DateTimeFormatUtil.luisDateTime(resultDateTime)); + } + + ret.setFutureValue(resultDateTime); + ret.setPastValue(resultDateTime); + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(durationParseResult); + + ret.setSubDateTimeEntities(subDateTimeEntities); + ret.setSuccess(true); + } + + return ret; + } + + public static List extractorDurationWithBeforeAndAfter(String text, ExtractResult er, List result, + IDateTimeUtilityConfiguration utilityConfiguration) { + int pos = er.getStart() + er.getLength(); + if (pos <= text.length()) { + String afterString = text.substring(pos); + String beforeString = text.substring(0, er.getStart()); + boolean isTimeDuration = RegExpUtility.getMatches(utilityConfiguration.getTimeUnitRegex(), + er.getText()).length != 0; + + MatchingUtilResult resultIndex = MatchingUtil.getAgoLaterIndex(afterString, + utilityConfiguration.getAgoRegex()); + if (resultIndex.result) { + // We don't support cases like "5 minutes from today" for now + // Cases like "5 minutes ago" or "5 minutes from now" are supported + // Cases like "2 days before today" or "2 weeks from today" are also supported + Optional match = Arrays.stream(RegExpUtility.getMatches(utilityConfiguration.getAgoRegex(), afterString)).findFirst(); + boolean isDayMatchInAfterString = match.isPresent() && !match.get().getGroup("day").value.equals(""); + + if (!(isTimeDuration && isDayMatchInAfterString)) { + result.add(new Token(er.getStart(), er.getStart() + er.getLength() + resultIndex.index)); + } + } else { + resultIndex = MatchingUtil.getAgoLaterIndex(afterString, utilityConfiguration.getLaterRegex()); + if (resultIndex.result) { + Optional match = Arrays.stream(RegExpUtility.getMatches(utilityConfiguration.getLaterRegex(), afterString)).findFirst(); + boolean isDayMatchInAfterString = match.isPresent() && !match.get().getGroup("day").value.equals(""); + + if (!(isTimeDuration && isDayMatchInAfterString)) { + result.add(new Token(er.getStart(), er.getStart() + er.getLength() + resultIndex.index)); + } + } else { + resultIndex = MatchingUtil.getTermIndex(beforeString, utilityConfiguration.getInConnectorRegex()); + if (resultIndex.result) { + // For range unit like "week, month, year", it should output dateRange or + // datetimeRange + Optional match = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getRangeUnitRegex(), er.getText())) + .findFirst(); + if (!match.isPresent()) { + if (er.getStart() >= resultIndex.index) { + result.add(new Token(er.getStart() - resultIndex.index, er.getStart() + er.getLength())); + } + } + } else { + resultIndex = MatchingUtil.getTermIndex(beforeString, + utilityConfiguration.getWithinNextPrefixRegex()); + if (resultIndex.result) { + // For range unit like "week, month, year, day, second, minute, hour", it should + // output dateRange or datetimeRange + Optional matchDateUnitRegex = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getDateUnitRegex(), er.getText())) + .findFirst(); + Optional matchTimeUnitRegex = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getTimeUnitRegex(), er.getText())) + .findFirst(); + if (!matchDateUnitRegex.isPresent() && !matchTimeUnitRegex.isPresent()) { + if (er.getStart() >= resultIndex.index) { + result.add(new Token(er.getStart() - resultIndex.index, er.getStart() + er.getLength())); + } + } + } + } + } + } + } + + return result; + } + + private static boolean isDayMatchInAfterString(String text, Pattern pattern, String group) { + Optional match = Arrays.stream(RegExpUtility.getMatches(pattern, text)).findFirst(); + + if (match.isPresent()) { + MatchGroup matchGroup = match.get().getGroup(group); + return !StringUtility.isNullOrEmpty(matchGroup.value); + } + + return false; + } + + public enum AgoLaterMode { + DATE, DATETIME + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java new file mode 100644 index 000000000..2fe1304c5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.utilities.Match; + +import java.util.Optional; + +public class ConditionalMatch { + + private final Optional match; + private final boolean success; + + public ConditionalMatch(Optional match, boolean success) { + this.match = match; + this.success = success; + } + + public Optional getMatch() { + + return match; + } + + public boolean getSuccess() { + + return success; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java new file mode 100644 index 000000000..56e1a9ed6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java @@ -0,0 +1,165 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import java.time.LocalDateTime; +import java.util.HashMap; + +import org.javatuples.Pair; + +// Currently only Year is enabled as context, we may support Month or Week in the future +public class DateContext { + private int year = Constants.InvalidYear; + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public DateTimeParseResult processDateEntityParsingResult(DateTimeParseResult originalResult) { + if (!isEmpty()) { + originalResult.setTimexStr(TimexUtility.setTimexWithContext(originalResult.getTimexStr(), this)); + originalResult.setValue(processDateEntityResolution((DateTimeResolutionResult)originalResult.getValue())); + } + + return originalResult; + } + + public DateTimeResolutionResult processDateEntityResolution(DateTimeResolutionResult resolutionResult) { + if (!isEmpty()) { + resolutionResult.setTimex(TimexUtility.setTimexWithContext(resolutionResult.getTimex(), this)); + resolutionResult.setFutureValue(setDateWithContext((LocalDateTime)resolutionResult.getFutureValue())); + resolutionResult.setPastValue(setDateWithContext((LocalDateTime)resolutionResult.getPastValue())); + } + + return resolutionResult; + } + + public DateTimeResolutionResult processDatePeriodEntityResolution(DateTimeResolutionResult resolutionResult) { + if (!isEmpty()) { + resolutionResult.setTimex(TimexUtility.setTimexWithContext(resolutionResult.getTimex(), this)); + resolutionResult.setFutureValue(setDateRangeWithContext((Pair)resolutionResult.getFutureValue())); + resolutionResult.setPastValue(setDateRangeWithContext((Pair)resolutionResult.getPastValue())); + } + + return resolutionResult; + } + + // Generate future/past date for cases without specific year like "Feb 29th" + public static HashMap generateDates(boolean noYear, LocalDateTime referenceDate, int year, int month, int day) { + HashMap result = new HashMap<>(); + LocalDateTime futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + LocalDateTime pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + int futureYear = year; + int pastYear = year; + if (noYear) { + if (isFeb29th(year, month, day)) { + if (isLeapYear(year)) { + if (futureDate.compareTo(referenceDate) < 0) { + futureDate = DateUtil.safeCreateFromMinValue(futureYear + 4, month, day); + } else { + pastDate = DateUtil.safeCreateFromMinValue(pastYear - 4, month, day); + } + } else { + pastYear = pastYear >> 2 << 2; + if (!isLeapYear(pastYear)) { + pastYear -= 4; + } + + futureYear = pastYear + 4; + if (!isLeapYear(futureYear)) { + futureYear += 4; + } + futureDate = DateUtil.safeCreateFromMinValue(futureYear, month, day); + pastDate = DateUtil.safeCreateFromMinValue(pastYear, month, day); + } + } else { + if (futureDate.compareTo(referenceDate) < 0 && DateUtil.isValidDate(year, month, day)) { + futureDate = DateUtil.safeCreateFromMinValue(year + 1, month, day); + } + + if (pastDate.compareTo(referenceDate) >= 0 && DateUtil.isValidDate(year, month, day)) { + pastDate = DateUtil.safeCreateFromMinValue(year - 1, month, day); + } + } + } + result.put(Constants.FutureDate, futureDate); + result.put(Constants.PastDate, pastDate); + return result; + } + + private static boolean isLeapYear(int year) { + return (((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0))); + } + + private static boolean isFeb29th(int year, int month, int day) { + return month == 2 && day == 29; + } + + public static boolean isFeb29th(LocalDateTime date) { + return date.getMonthValue() == 2 && date.getDayOfMonth() == 29; + } + + // This method is to ensure the year of begin date is same with the end date in no year situation. + public HashMap syncYear(DateTimeParseResult pr1, DateTimeParseResult pr2) { + if (this.isEmpty()) { + int futureYear; + int pastYear; + if (isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue())) { + futureYear = ((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue()).getYear(); + pastYear = ((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getPastValue()).getYear(); + pr2.setValue(syncYearResolution((DateTimeResolutionResult)pr2.getValue(), futureYear, pastYear)); + } else if (isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue())) { + futureYear = ((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue()).getYear(); + pastYear = ((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getPastValue()).getYear(); + pr1.setValue(syncYearResolution((DateTimeResolutionResult)pr1.getValue(), futureYear, pastYear)); + } + } + + HashMap result = new HashMap<>(); + result.put(Constants.ParseResult1, pr1); + result.put(Constants.ParseResult2, pr2); + return result; + } + + public DateTimeResolutionResult syncYearResolution(DateTimeResolutionResult resolutionResult, int futureYear, int pastYear) { + resolutionResult.setFutureValue(setDateWithContext((LocalDateTime)resolutionResult.getFutureValue(), futureYear)); + resolutionResult.setPastValue(setDateWithContext((LocalDateTime)resolutionResult.getPastValue(), pastYear)); + return resolutionResult; + } + + public boolean isEmpty() { + return this.year == Constants.InvalidYear; + } + + // This method is to ensure the begin date is less than the end date + // As DateContext only support common Year as context, so decrease year part of begin date to ensure the begin date is less than end date + public LocalDateTime swiftDateObject(LocalDateTime beginDate, LocalDateTime endDate) { + if (beginDate.isAfter(endDate)) { + beginDate = beginDate.plusYears(-1); + } + + return beginDate; + } + + private LocalDateTime setDateWithContext(LocalDateTime originalDate) { + return setDateWithContext(originalDate, this.year); + } + + private LocalDateTime setDateWithContext(LocalDateTime originalDate, int year) { + if (!DateUtil.isDefaultValue(originalDate)) { + return DateUtil.safeCreateFromMinValue(year, originalDate.getMonthValue(), originalDate.getDayOfMonth()); + } + return originalDate; + } + + private Pair setDateRangeWithContext(Pair originalDateRange) { + LocalDateTime startDate = setDateWithContext(originalDateRange.getValue0()); + LocalDateTime endDate = setDateWithContext(originalDateRange.getValue1()); + + return new Pair<>(startDate, endDate); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java new file mode 100644 index 000000000..f6b194402 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java @@ -0,0 +1,248 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.utilities.IntegerUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.IsoFields; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DateTimeFormatUtil { + + private static final Pattern HourTimexRegex = Pattern.compile("(? 0) { + result = String.format("%s%sH", result, timeSpan.toHours() % 24); + } + + if (timeSpan.toMinutes() % 60 > 0) { + result = String.format("%s%sM", result, timeSpan.toMinutes() % 60); + } + + if (timeSpan.get(ChronoUnit.SECONDS) % 60 > 0) { + result = String.format("%s%sS", result, timeSpan.get(ChronoUnit.SECONDS) % 60); + } + + if (timeSpan.toMinutes() % 60 > 0) { + result = String.format("%s%sM", result, timeSpan.toMinutes() % 60); + } + + if (timeSpan.get(ChronoUnit.SECONDS) % 60 > 0) { + result = String.format("%s%sS", result, timeSpan.get(ChronoUnit.SECONDS) % 60); + } + + return timeSpan.toString(); + } + + public static String toPm(String timeStr) { + boolean hasT = false; + if (timeStr.startsWith("T")) { + hasT = true; + timeStr = timeStr.substring(1); + } + + String[] splited = timeStr.split(":"); + int hour = Integer.parseInt(splited[0]); + hour = hour >= Constants.HalfDayHourCount ? hour - Constants.HalfDayHourCount : hour + Constants.HalfDayHourCount; + splited[0] = String.format("%02d", hour); + timeStr = String.join(":", splited); + + return hasT ? "T" + timeStr : timeStr; + } + + public static String allStringToPm(String timeStr) { + Match[] matches = RegExpUtility.getMatches(HourTimexRegex, timeStr); + ArrayList splited = new ArrayList<>(); + + int lastPos = 0; + for (Match match : matches) { + if (lastPos != match.index) { + splited.add(timeStr.substring(lastPos, match.index)); + } + splited.add(timeStr.substring(match.index, match.index + match.length)); + lastPos = match.index + match.length; + } + + if (!StringUtility.isNullOrEmpty(timeStr.substring(lastPos))) { + splited.add(timeStr.substring(lastPos)); + } + + for (int i = 0; i < splited.size(); i++) { + if (HourTimexRegex.matcher(splited.get(i)).lookingAt()) { + splited.set(i, toPm(splited.get(i))); + } + } + + // Modify weekDay timex for the cases which cross day boundary + if (splited.size() >= 4) { + Matcher weekDayStartMatch = WeekDayTimexRegex.matcher(splited.get(0)); + Matcher weekDayEndMatch = WeekDayTimexRegex.matcher(splited.get(2)); + Matcher hourStartMatch = HourTimexRegex.matcher(splited.get(1)); + Matcher hourEndMatch = HourTimexRegex.matcher(splited.get(3)); + + String weekDayStartStr = weekDayStartMatch.find() ? weekDayStartMatch.group(1) : ""; + String weekDayEndStr = weekDayEndMatch.find() ? weekDayEndMatch.group(1) : ""; + String hourStartStr = hourStartMatch.find() ? hourStartMatch.group(1) : ""; + String hourEndStr = hourEndMatch.find() ? hourEndMatch.group(1) : ""; + + if (IntegerUtility.canParse(weekDayStartStr) && + IntegerUtility.canParse(weekDayEndStr) && + IntegerUtility.canParse(hourStartStr) && + IntegerUtility.canParse(hourEndStr)) { + int weekDayStart = Integer.parseInt(weekDayStartStr); + int weekDayEnd = Integer.parseInt(weekDayEndStr); + int hourStart = Integer.parseInt(hourStartStr); + int hourEnd = Integer.parseInt(hourEndStr); + + if (hourEnd < hourStart && weekDayStart == weekDayEnd) { + weekDayEnd = weekDayEnd == Constants.WeekDayCount ? 1 : weekDayEnd + 1; + splited.set(2, splited.get(2).substring(0, weekDayEndMatch.start(1)) + weekDayEnd); + } + } + } + + return String.join("", splited); + } + + public static String toIsoWeekTimex(LocalDateTime date) { + int weekNum = LocalDate.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth()).get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); + return String.format("%04d-W%02d", date.getYear(), weekNum); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java new file mode 100644 index 000000000..943ecf02e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java @@ -0,0 +1,134 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.util.List; +import java.util.Map; + +public class DateTimeResolutionResult { + + private Boolean success; + private String timex; + private Boolean isLunar; + private String mod; + private Boolean hasRangeChangingMod; + private String comment; + + private Map futureResolution; + private Map pastResolution; + + private Object futureValue; + private Object pastValue; + + private List subDateTimeEntities; + + private TimeZoneResolutionResult timeZoneResolution; + + private List list; + + public DateTimeResolutionResult() { + success = hasRangeChangingMod = false; + } + + public Boolean getSuccess() { + return this.success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getTimex() { + return this.timex; + } + + public void setTimex(String timex) { + this.timex = timex; + } + + public Boolean getIsLunar() { + return this.isLunar; + } + + public void setIsLunar(Boolean isLunar) { + this.isLunar = isLunar; + } + + public String getMod() { + return this.mod; + } + + public void setMod(String mod) { + this.mod = mod; + } + + public Boolean getHasRangeChangingMod() { + return this.hasRangeChangingMod; + } + + public void setHasRangeChangingMod(Boolean hasRangeChangingMod) { + this.hasRangeChangingMod = hasRangeChangingMod; + } + + public String getComment() { + return this.comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public Map getFutureResolution() { + return this.futureResolution; + } + + public void setFutureResolution(Map futureResolution) { + this.futureResolution = futureResolution; + } + + public Map getPastResolution() { + return this.pastResolution; + } + + public void setPastResolution(Map pastResolution) { + this.pastResolution = pastResolution; + } + + public Object getFutureValue() { + return this.futureValue; + } + + public void setFutureValue(Object futureValue) { + this.futureValue = futureValue; + } + + public Object getPastValue() { + return this.pastValue; + } + + public void setPastValue(Object pastValue) { + this.pastValue = pastValue; + } + + public List getSubDateTimeEntities() { + return this.subDateTimeEntities; + } + + public void setSubDateTimeEntities(List subDateTimeEntities) { + this.subDateTimeEntities = subDateTimeEntities; + } + + public TimeZoneResolutionResult getTimeZoneResolution() { + return this.timeZoneResolution; + } + + public void setTimeZoneResolution(TimeZoneResolutionResult timeZoneResolution) { + this.timeZoneResolution = timeZoneResolution; + } + + public List getList() { + return this.list; + } + + public void setList(List list) { + this.list = list; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java new file mode 100644 index 000000000..0e1a26c19 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java @@ -0,0 +1,156 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalField; +import java.time.temporal.WeekFields; +import java.util.Locale; + +public class DateUtil { + + public static LocalDateTime safeCreateFromValue(LocalDateTime datetime, int year, int month, int day, int hour, int minute, int second) { + if (isValidDate(year, month, day) && isValidTime(hour, minute, second)) { + datetime = safeCreateFromValue(datetime, year, month, day); + datetime = datetime.plusHours(hour - datetime.getHour()); + datetime = datetime.plusMinutes((minute - datetime.getMinute())); + datetime = datetime.plusSeconds(second - datetime.getSecond()); + } + + return datetime; + } + + public static LocalDateTime safeCreateFromValue(LocalDateTime datetime, int year, int month, int day) { + if (isValidDate(year, month, day)) { + datetime = datetime.plusYears(year - datetime.getYear()); + datetime = datetime.plusMonths((month - datetime.getMonthValue())); + datetime = datetime.plusDays(day - datetime.getDayOfMonth()); + } + + return datetime; + } + + public static LocalDateTime safeCreateFromMinValue(int year, int month, int day) { + return safeCreateFromValue(minValue(), year, month, day, 0, 0, 0); + } + + public static LocalDateTime safeCreateFromMinValue(int year, int month, int day, int hour, int minute, int second) { + return safeCreateFromValue(minValue(), year, month, day, hour, minute, second); + } + + public static LocalDateTime safeCreateFromMinValue(LocalDate date, LocalTime time) { + return safeCreateFromValue(minValue(), + date.getYear(), date.getMonthValue(), date.getDayOfMonth(), + time.getHour(), time.getMinute(), time.getSecond() + ); + } + + public static LocalDateTime minValue() { + return LocalDateTime.of(1, 1, 1, 0, 0, 0, 0); + } + + public static Boolean isValidDate(int year, int month, int day) { + if (year < 1 || year > 9999) { + return false; + } + + Integer[] validDays = { + 31, + year % 4 == 0 && year % 100 != 0 || year % 400 == 0 ? 29 : 28, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31 + }; + + return month >= 1 && month <= 12 && day >= 1 && day <= validDays[month - 1]; + } + + public static boolean isValidTime(int hour, int minute, int second) { + return 0 <= hour && hour <= 23 && + 0 <= minute && minute <= 59 && + 0 <= second && second <= 59; + } + + public static boolean isDefaultValue(LocalDateTime date) { + return date.equals(DateUtil.minValue()); + } + + private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .toFormatter(); + + public static LocalDateTime tryParse(String date) { + try { + return LocalDateTime.parse(date, DATE_TIME_FORMATTER); + } catch (DateTimeParseException ex) { + return null; + } + } + + public static LocalDateTime next(LocalDateTime from, int dayOfWeek) { + int start = from.getDayOfWeek().getValue(); + + if (start == 0) { + start = 7; + } + + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + + return from.plusDays(dayOfWeek - start + 7); + } + + public static LocalDateTime thisDate(LocalDateTime from, int dayOfWeek) { + int start = from.getDayOfWeek().getValue(); + + if (start == 0) { + start = 7; + } + + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + + return from.plusDays(dayOfWeek - start); + } + + public static LocalDateTime last(LocalDateTime from, int dayOfWeek) { + int start = from.getDayOfWeek().getValue(); + + if (start == 0) { + start = 7; + } + + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + + return from.plusDays(dayOfWeek - start - 7); + } + + public static LocalDateTime plusPeriodInNanos(LocalDateTime reference, double period, ChronoUnit unit) { + long nanos = unit.getDuration().toNanos(); + return reference.plusNanos(Math.round(nanos * period)); + } + + public static int weekOfYear(LocalDateTime date) { + TemporalField woy = WeekFields.ISO.weekOfYear(); + return date.get(woy); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java new file mode 100644 index 000000000..0b2a9e40a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java @@ -0,0 +1,187 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.utilities.DoubleUtility; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +public class DurationParsingUtil { + public static boolean isTimeDurationUnit(String unitStr) { + boolean result = false; + switch (unitStr) { + case "H": + result = true; + break; + case "M": + result = true; + break; + case "S": + result = true; + break; + default: + break; + } + return result; + } + + public static boolean isMultipleDuration(String timex) { + ImmutableMap map = resolveDurationTimex(timex); + return map.size() > 1; + } + + public static boolean isDateDuration(String timex) { + ImmutableMap map = resolveDurationTimex(timex); + + for (String unit : map.keySet()) { + if (isTimeDurationUnit(unit)) { + return false; + } + } + + return true; + } + + public static LocalDateTime shiftDateTime(String timex, LocalDateTime reference, boolean future) { + ImmutableMap timexUnitMap = resolveDurationTimex(timex); + + return getShiftResult(timexUnitMap, reference, future); + } + + public static LocalDateTime getShiftResult(ImmutableMap timexUnitMap, LocalDateTime reference, boolean future) { + LocalDateTime result = reference; + int futureOrPast = future ? 1 : -1; + for (Map.Entry pair : timexUnitMap.entrySet()) { + String unit = pair.getKey(); + ChronoUnit chronoUnit; + Double number = pair.getValue(); + + switch (unit) { + case "H": + chronoUnit = ChronoUnit.HOURS; + break; + case "M": + chronoUnit = ChronoUnit.MINUTES; + break; + case "S": + chronoUnit = ChronoUnit.SECONDS; + break; + case Constants.TimexDay: + chronoUnit = ChronoUnit.DAYS; + break; + case Constants.TimexWeek: + chronoUnit = ChronoUnit.WEEKS; + break; + case Constants.TimexMonthFull: + chronoUnit = null; + result = result.plusMonths(Math.round(number * futureOrPast)); + break; + case Constants.TimexYear: + chronoUnit = null; + result = result.plusYears(Math.round(number * futureOrPast)); + break; + case Constants.TimexBusinessDay: + chronoUnit = null; + result = getNthBusinessDay(result, Math.round(number.floatValue()), future).result; + break; + + default: + return result; + } + if (chronoUnit != null) { + result = DateUtil.plusPeriodInNanos(result, number * futureOrPast, chronoUnit); + } + } + return result; + } + + public static NthBusinessDayResult getNthBusinessDay(LocalDateTime startDate, int number, boolean isFuture) { + LocalDateTime date = startDate; + List dateList = new ArrayList<>(); + dateList.add(date); + + for (int i = 0; i < number; i++) { + date = getNextBusinessDay(date, isFuture); + dateList.add(date); + } + + if (!isFuture) { + Collections.reverse(dateList); + } + + return new NthBusinessDayResult(date, dateList); + + } + + public static LocalDateTime getNextBusinessDay(LocalDateTime startDate) { + return getNextBusinessDay(startDate, true); + } + + // By design it currently does not take holidays into account + public static LocalDateTime getNextBusinessDay(LocalDateTime startDate, boolean isFuture) { + int dateIncrement = isFuture ? 1 : -1; + LocalDateTime date = startDate.plusDays(dateIncrement); + + while (date.getDayOfWeek().equals(DayOfWeek.SATURDAY) || date.getDayOfWeek().equals(DayOfWeek.SUNDAY)) { + date = date.plusDays(dateIncrement); + } + + return date; + } + + private static ImmutableMap resolveDurationTimex(String timex) { + Builder resultBuilder = ImmutableMap.builder(); + + // resolve duration timex, such as P21DT2H(21 days 2 hours) + String durationStr = timex.replace('P', '\0'); + int numberStart = 0; + boolean isTime = false; + + // Resolve business days + if (durationStr.endsWith(Constants.TimexBusinessDay)) { + if (DoubleUtility.canParse(durationStr.substring(0, durationStr.length() - 2))) { + + double numVal = Double.parseDouble(durationStr.substring(0, durationStr.length() - 2)); + resultBuilder.put(Constants.TimexBusinessDay, numVal); + } + + return resultBuilder.build(); + } + + for (int i = 0; i < durationStr.length(); i++) { + if (Character.isLetter(durationStr.charAt(i))) { + if (durationStr.charAt(i) == 'T') { + isTime = true; + } else { + String numStr = durationStr.substring(numberStart, i); + + try { + Double number = Double.parseDouble(numStr); + String srcTimexUnit = durationStr.substring(i, i + 1); + + if (!isTime && srcTimexUnit.equals("M")) { + srcTimexUnit = "MON"; + } + + resultBuilder.put(srcTimexUnit, number); + + } catch (NumberFormatException e) { + return resultBuilder.build(); + } + + } + numberStart = i + 1; + } + } + + return resultBuilder.build(); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java new file mode 100644 index 000000000..2e424c492 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java @@ -0,0 +1,22 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDateTime; +import java.util.List; + +public class GetModAndDateResult { + public final LocalDateTime beginDate; + public final LocalDateTime endDate; + public final String mod; + public final List dateList; + + public GetModAndDateResult(LocalDateTime beginDate, LocalDateTime endDate, String mod, List dateList) { + this.beginDate = beginDate; + this.endDate = endDate; + this.mod = mod; + this.dateList = dateList; + } + + public GetModAndDateResult() { + this(null, null, "", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java new file mode 100644 index 000000000..260d816e4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDateTime; + +public class HolidayFunctions { + + public static LocalDateTime calculateHolidayByEaster(int year) { + return calculateHolidayByEaster(year, 0); + } + + public static LocalDateTime calculateHolidayByEaster(int year, int days) { + + int day = 0; + int month = 3; + + int g = year % 19; + int c = year / 100; + int h = (c - (int)(c / 4) - (int)(((8 * c) + 13) / 25) + (19 * g) + 15) % 30; + int i = h - ((int)(h / 28) * (1 - ((int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11)))); + + day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28; + + if (day > 31) { + month++; + day -= 31; + } + + return DateUtil.safeCreateFromMinValue(year, month, day).plusDays(days); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java new file mode 100644 index 000000000..5ea05f65b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java @@ -0,0 +1,28 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.util.regex.Pattern; + +public interface IDateTimeUtilityConfiguration { + + Pattern getAgoRegex(); + + Pattern getLaterRegex(); + + Pattern getInConnectorRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getRangeUnitRegex(); + + Pattern getTimeUnitRegex(); + + Pattern getDateUnitRegex(); + + Pattern getAmDescRegex(); + + Pattern getPmDescRegex(); + + Pattern getAmPmDescRegex(); + + Pattern getCommonDatePrefixRegex(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java new file mode 100644 index 000000000..6f9c0bd39 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class MatchedTimexResult { + private boolean result; + private String timex; + + public MatchedTimexResult(boolean result, String timex) { + this.result = result; + this.timex = timex; + } + + public MatchedTimexResult() { + this(false, ""); + } + + public boolean getResult() { + return result; + } + + public String getTimex() { + return timex; + } + + public void setResult(boolean result) { + this.result = result; + } + + public void setTimex(String timex) { + this.timex = timex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java new file mode 100644 index 000000000..e700d2f1f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java @@ -0,0 +1,106 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.extractors.config.ProcessedSuperfluousWords; +import com.microsoft.recognizers.text.matcher.MatchResult; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class MatchingUtil { + + public static MatchingUtilResult getAgoLaterIndex(String text, Pattern pattern) { + int index = -1; + ConditionalMatch match = RegexExtension.matchBegin(pattern, text, true); + + if (match.getSuccess()) { + index = match.getMatch().get().index + match.getMatch().get().length; + return new MatchingUtilResult(true, index); + } + + return new MatchingUtilResult(); + } + + public static MatchingUtilResult getTermIndex(String text, Pattern pattern) { + String[] parts = text.trim().toLowerCase().split(" "); + String lastPart = parts[parts.length - 1]; + Optional match = Arrays.stream(RegExpUtility.getMatches(pattern, lastPart)).findFirst(); + + if (match.isPresent()) { + int index = text.length() - text.toLowerCase().lastIndexOf(match.get().value); + return new MatchingUtilResult(true, index); + } + + return new MatchingUtilResult(); + } + + public static Boolean containsAgoLaterIndex(String text, Pattern regex) { + MatchingUtilResult result = getAgoLaterIndex(text, regex); + return result.result; + } + + public static Boolean containsTermIndex(String text, Pattern regex) { + MatchingUtilResult result = getTermIndex(text, regex); + return result.result; + } + + // Temporary solution for remove superfluous words only under the Preview mode + public static ProcessedSuperfluousWords preProcessTextRemoveSuperfluousWords(String text, StringMatcher matcher) { + List> superfluousWordMatches = removeSubMatches(matcher.find(text)); + int bias = 0; + + for (MatchResult match : superfluousWordMatches) { + text = text.substring(0, match.getStart() - bias) + text.substring(match.getEnd() - bias); + bias += match.getLength(); + } + + return new ProcessedSuperfluousWords(text, superfluousWordMatches); + } + + // Temporary solution for recover superfluous words only under the Preview mode + public static List posProcessExtractionRecoverSuperfluousWords(List extractResults, + Iterable> superfluousWordMatches, String originText) { + for (MatchResult match : superfluousWordMatches) { + int index = 0; + for (ExtractResult extractResult : extractResults.toArray(new ExtractResult[0])) { + int extractResultEnd = extractResult.getStart() + extractResult.getLength(); + if (match.getStart() > extractResult.getStart() && extractResultEnd >= match.getStart()) { + extractResult.setLength(extractResult.getLength() + match.getLength()); + extractResults.set(index, extractResult); + } + + if (match.getStart() <= extractResult.getStart()) { + extractResult.setStart(extractResult.getStart() + match.getLength()); + extractResults.set(index, extractResult); + } + index++; + } + } + + int index = 0; + for (ExtractResult er : extractResults.toArray(new ExtractResult[0])) { + er.setText(originText.substring(er.getStart(), er.getStart() + er.getLength())); + extractResults.set(index, er); + index++; + } + + return extractResults; + } + + public static List> removeSubMatches(Iterable> matchResults) { + + return StreamSupport.stream(matchResults.spliterator(), false) + .filter(item -> !StreamSupport.stream(matchResults.spliterator(), false) + .anyMatch(ritem -> (ritem.getStart() < item.getStart() && ritem.getEnd() >= item.getEnd()) || + (ritem.getStart() <= item.getStart() && ritem.getEnd() > item.getEnd()))) + .collect(Collectors.toCollection(ArrayList::new)); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java new file mode 100644 index 000000000..b5d06180c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class MatchingUtilResult { + public final boolean result; + public final int index; + + public MatchingUtilResult(boolean result, int index) { + this.result = result; + this.index = index; + } + + public MatchingUtilResult() { + this(false, -1); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java new file mode 100644 index 000000000..155bc9b6d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDateTime; +import java.util.List; + +public class NthBusinessDayResult { + public final LocalDateTime result; + public final List dateList; + + public NthBusinessDayResult(LocalDateTime result, List dateList) { + this.result = result; + this.dateList = dateList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java new file mode 100644 index 000000000..35fc10d22 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class RangeTimexComponents { + public String beginTimex; + + public String endTimex; + + public String durationTimex; + + public Boolean isValid = false; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java new file mode 100644 index 000000000..b4d4b8dc1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java @@ -0,0 +1,58 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public abstract class RegexExtension { + // Regex match with match length equals to text length + public static boolean isExactMatch(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).findFirst(); + int length = trim ? text.trim().length() : text.length(); + + return (match.isPresent() && match.get().length == length); + } + + // We can't trim before match as we may use the match index later + public static ConditionalMatch matchExact(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).findFirst(); + int length = trim ? text.trim().length() : text.length(); + + return new ConditionalMatch(match, (match.isPresent() && match.get().length == length)); + } + + // We can't trim before match as we may use the match index later + public static ConditionalMatch matchEnd(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).reduce((f, s) -> s); + String strAfter = ""; + if (match.isPresent()) { + strAfter = text.substring(match.get().index + match.get().length); + + if (trim) { + strAfter = strAfter.trim(); + } + } + + return new ConditionalMatch(match, (match.isPresent() && StringUtility.isNullOrEmpty(strAfter))); + } + + // We can't trim before match as we may use the match index later + public static ConditionalMatch matchBegin(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).findFirst(); + String strBefore = ""; + + if (match.isPresent()) { + strBefore = text.substring(0, match.get().index); + + if (trim) { + strBefore = strBefore.trim(); + } + } + + return new ConditionalMatch(match, (match.isPresent() && StringUtility.isNullOrEmpty(strBefore))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java new file mode 100644 index 000000000..01607aea1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; + +public abstract class StringExtension { + public static String normalize(String text, ImmutableMap dic) { + for (ImmutableMap.Entry keyPair : dic.entrySet()) { + text = text.replace(keyPair.getKey(), keyPair.getValue()); + } + + return text; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java new file mode 100644 index 000000000..1447e81a3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java @@ -0,0 +1,50 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class TimeOfDayResolutionResult { + private String timex; + private int beginHour; + private int endHour; + private int endMin; + + public TimeOfDayResolutionResult(String timex, int beginHour, int endHour, int endMin) { + this.timex = timex; + this.beginHour = beginHour; + this.endHour = endHour; + this.endMin = endMin; + } + + public TimeOfDayResolutionResult() { + } + + public String getTimex() { + return timex; + } + + public void setTimex(String timex) { + this.timex = timex; + } + + public int getBeginHour() { + return beginHour; + } + + public void setBeginHour(int beginHour) { + this.beginHour = beginHour; + } + + public int getEndHour() { + return endHour; + } + + public void setEndHour(int endHour) { + this.endHour = endHour; + } + + public int getEndMin() { + return endMin; + } + + public void setEndMin(int endMin) { + this.endMin = endMin; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java new file mode 100644 index 000000000..7305ec7ff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java @@ -0,0 +1,26 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class TimeZoneResolutionResult { + + private final String value; + private final Integer utcOffsetMins; + private final String timeZoneText; + + public TimeZoneResolutionResult(String value, Integer utcOffsetMins, String timeZoneText) { + this.value = value; + this.utcOffsetMins = utcOffsetMins; + this.timeZoneText = timeZoneText; + } + + public String getValue() { + return this.value; + } + + public Integer getUtcOffsetMins() { + return this.utcOffsetMins; + } + + public String getTimeZoneText() { + return this.timeZoneText; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java new file mode 100644 index 000000000..7720f1dca --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java @@ -0,0 +1,64 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class TimeZoneUtility { + public static List mergeTimeZones(List originalErs, List timeZoneErs, String text) { + + int index = 0; + for (ExtractResult er : originalErs.toArray(new ExtractResult[0])) { + for (ExtractResult timeZoneEr : timeZoneErs) { + int begin = er.getStart() + er.getLength(); + int end = timeZoneEr.getStart(); + + if (begin < end) { + String gapText = text.substring(begin, end); + + if (StringUtility.isNullOrWhiteSpace(gapText)) { + int length = timeZoneEr.getStart() + timeZoneEr.getLength() - er.getStart(); + Map data = new HashMap<>(); + data.put(Constants.SYS_DATETIME_TIMEZONE, timeZoneEr); + + originalErs.set(index, new ExtractResult(er.getStart(), length, text.substring(er.getStart(), er.getStart() + length), er.getType(), data)); + } + } + + // Make sure timezone info propagates to longer span entity. + if (er.isOverlap(timeZoneEr)) { + Map data = new HashMap<>(); + data.put(Constants.SYS_DATETIME_TIMEZONE, timeZoneEr); + er.setData(data); + } + } + index++; + } + + return originalErs; + } + + public static boolean shouldResolveTimeZone(ExtractResult er, DateTimeOptions options) { + boolean enablePreview = options.match(DateTimeOptions.EnablePreview); + if (!enablePreview) { + return enablePreview; + } + + boolean hasTimeZoneData = false; + + if (er.getData() instanceof Map) { + Map metadata = (HashMap)er.getData(); + + if (metadata.containsKey(Constants.SYS_DATETIME_TIMEZONE)) { + hasTimeZoneData = true; + } + } + + return hasTimeZoneData; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java new file mode 100644 index 000000000..5c643317f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java @@ -0,0 +1,314 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; +import com.microsoft.recognizers.text.datetime.DateTimeResolutionKey; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TimexUtility { + private static final HashMap DatePeriodTimexTypeToTimexSuffix = new HashMap() { + { + put(DatePeriodTimexType.ByDay, Constants.TimexDay); + put(DatePeriodTimexType.ByWeek, Constants.TimexWeek); + put(DatePeriodTimexType.ByMonth, Constants.TimexMonth); + put(DatePeriodTimexType.ByYear, Constants.TimexYear); + } + }; + + public static String generateCompoundDurationTimex(Map unitToTimexComponents, ImmutableMap unitValueMap) { + List unitList = new ArrayList<>(unitToTimexComponents.keySet()); + unitList.sort((x, y) -> unitValueMap.get(x) < unitValueMap.get(y) ? 1 : -1); + boolean isTimeDurationAlreadyExist = false; + StringBuilder timexBuilder = new StringBuilder(Constants.GeneralPeriodPrefix); + + for (String unitKey : unitList) { + String timexComponent = unitToTimexComponents.get(unitKey); + + // The Time Duration component occurs first time + if (!isTimeDurationAlreadyExist && isTimeDurationTimex(timexComponent)) { + timexBuilder.append(Constants.TimeTimexPrefix); + timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); + isTimeDurationAlreadyExist = true; + } else { + timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); + } + } + return timexBuilder.toString(); + } + + private static boolean isTimeDurationTimex(String timex) { + return timex.startsWith(Constants.GeneralPeriodPrefix + Constants.TimeTimexPrefix); + } + + private static String getDurationTimexWithoutPrefix(String timex) { + // Remove "PT" prefix for TimeDuration, Remove "P" prefix for DateDuration + return timex.substring(isTimeDurationTimex(timex) ? 2 : 1); + } + + public static String getDatePeriodTimexUnitCount(LocalDateTime begin, LocalDateTime end, + DatePeriodTimexType timexType, Boolean equalDurationLength) { + String unitCount = "XX"; + if (equalDurationLength) { + switch (timexType) { + case ByDay: + unitCount = StringUtility.format((double)ChronoUnit.HOURS.between(begin, end) / 24); + break; + case ByWeek: + unitCount = Long.toString(ChronoUnit.WEEKS.between(begin, end)); + break; + case ByMonth: + unitCount = Long.toString(ChronoUnit.MONTHS.between(begin, end)); + break; + default: + unitCount = new BigDecimal((end.getYear() - begin.getYear()) + (end.getMonthValue() - begin.getMonthValue()) / 12.0).stripTrailingZeros().toString(); + } + } + + return unitCount; + } + + public static String generateDatePeriodTimex(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType) { + + return generateDatePeriodTimex(begin, end, timexType, null, null); + } + + public static String generateDatePeriodTimex(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType, + LocalDateTime alternativeBegin, LocalDateTime alternativeEnd) { + Boolean equalDurationLength; + if (alternativeBegin == null || alternativeEnd == null) { + equalDurationLength = true; + } else { + equalDurationLength = Duration.between(begin, end).equals(Duration.between(alternativeBegin, alternativeEnd)); + } + + String unitCount = getDatePeriodTimexUnitCount(begin, end, timexType, equalDurationLength); + String datePeriodTimex = "P" + unitCount + DatePeriodTimexTypeToTimexSuffix.get(timexType); + return "(" + DateTimeFormatUtil.luisDate(begin, alternativeBegin) + "," + DateTimeFormatUtil.luisDate(end, alternativeEnd) + "," + datePeriodTimex + ")"; + } + + public static String generateDatePeriodTimexStr(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType, + String timex1, String timex2) { + boolean boundaryValid = !DateUtil.isDefaultValue(begin) && !DateUtil.isDefaultValue(end); + String unitCount = boundaryValid ? getDatePeriodTimexUnitCount(begin, end, timexType, true) : "X"; + String datePeriodTimex = "P" + unitCount + DatePeriodTimexTypeToTimexSuffix.get(timexType); + return String.format("(%s,%s,%s)", timex1, timex2, datePeriodTimex); + } + + public static String generateWeekTimex() { + return generateWeekTimex(null); + } + + public static String generateWeekTimex(LocalDateTime monday) { + + if (monday == null) { + return Constants.TimexFuzzyYear + Constants.DateTimexConnector + Constants.TimexFuzzyWeek; + } else { + return DateTimeFormatUtil.toIsoWeekTimex(monday); + } + } + + public static String generateWeekTimex(int weekNum) { + return "W" + String.format("%02d", weekNum); + } + + public static String generateWeekendTimex() { + return generateWeekendTimex(null); + } + + public static String generateWeekendTimex(LocalDateTime date) { + if (date == null) { + return Constants.TimexFuzzyYear + Constants.DateTimexConnector + Constants.TimexFuzzyWeek + Constants.DateTimexConnector + Constants.TimexWeekend; + } else { + return DateTimeFormatUtil.toIsoWeekTimex(date) + Constants.DateTimexConnector + Constants.TimexWeekend; + } + } + + public static String generateMonthTimex() { + return generateMonthTimex(null); + } + + public static String generateMonthTimex(LocalDateTime date) { + if (date == null) { + return Constants.TimexFuzzyYear + Constants.DateTimexConnector + Constants.TimexFuzzyMonth; + } else { + return String.format("%04d-%02d", date.getYear(), date.getMonthValue()); + } + } + + public static String generateYearTimex(int year) { + return DateTimeFormatUtil.luisDate(year); + } + + public static String generateYearTimex(int year, String specialYearPrefixes) { + String yearStr = DateTimeFormatUtil.luisDate(year); + return String.format("%s%s", specialYearPrefixes, yearStr); + } + + public static String generateDurationTimex(double number, String unitStr, boolean isLessThanDay) { + if (!Constants.TimexBusinessDay.equals(unitStr)) { + if (Constants.DECADE_UNIT.equals(unitStr)) { + number = number * 10; + unitStr = Constants.TimexYear; + } else if (Constants.FORTNIGHT_UNIT.equals(unitStr)) { + number = number * 2; + unitStr = Constants.TimexWeek; + } else { + unitStr = unitStr.substring(0, 1); + } + } + + return String.format("%s%s%s%s", + Constants.GeneralPeriodPrefix, + isLessThanDay ? Constants.TimeTimexPrefix : "", + StringUtility.format(number), + unitStr); + } + + public static DatePeriodTimexType getDatePeriodTimexType(String durationTimex) { + DatePeriodTimexType result; + + String minimumUnit = durationTimex.substring(durationTimex.length() - 1); + + switch (minimumUnit) { + case Constants.TimexYear: + result = DatePeriodTimexType.ByYear; + break; + case Constants.TimexMonth: + result = DatePeriodTimexType.ByMonth; + break; + case Constants.TimexWeek: + result = DatePeriodTimexType.ByWeek; + break; + default: + result = DatePeriodTimexType.ByDay; + break; + } + + return result; + } + + public static LocalDateTime offsetDateObject(LocalDateTime date, int offset, DatePeriodTimexType timexType) { + LocalDateTime result; + + switch (timexType) { + case ByYear: + result = date.plusYears(offset); + break; + case ByMonth: + result = date.plusMonths(offset); + break; + case ByWeek: + result = date.plusDays(7 * offset); + break; + case ByDay: + result = date.plusDays(offset); + break; + default: + result = date; + break; + } + + return result; + } + + public static TimeOfDayResolutionResult parseTimeOfDay(String tod) { + switch (tod) { + case Constants.EarlyMorning: + return new TimeOfDayResolutionResult(Constants.EarlyMorning, 4, 8, 0); + case Constants.Morning: + return new TimeOfDayResolutionResult(Constants.Morning, 8, 12, 0); + case Constants.Afternoon: + return new TimeOfDayResolutionResult(Constants.Afternoon, 12, 16, 0); + case Constants.Evening: + return new TimeOfDayResolutionResult(Constants.Evening, 16, 20, 0); + case Constants.Daytime: + return new TimeOfDayResolutionResult(Constants.Daytime, 8, 18, 0); + case Constants.BusinessHour: + return new TimeOfDayResolutionResult(Constants.BusinessHour, 8, 18, 0); + case Constants.Night: + return new TimeOfDayResolutionResult(Constants.Night, 20, 23, 59); + default: + return new TimeOfDayResolutionResult(); + } + } + + public static String combineDateAndTimeTimex(String dateTimex, String timeTimex) { + return dateTimex + timeTimex; + } + + public static String generateWeekOfYearTimex(int year, int weekNum) { + String weekTimex = generateWeekTimex(weekNum); + String yearTimex = DateTimeFormatUtil.luisDate(year); + + return yearTimex + "-" + weekTimex; + } + + public static String generateWeekOfMonthTimex(int year, int month, int weekNum) { + String weekTimex = generateWeekTimex(weekNum); + String monthTimex = DateTimeFormatUtil.luisDate(year, month); + + return monthTimex + "-" + weekTimex; + } + + public static String generateDateTimePeriodTimex(String beginTimex, String endTimex, String durationTimex) { + return "(" + beginTimex + "," + endTimex + "," + durationTimex + ")"; + } + + public static RangeTimexComponents getRangeTimexComponents(String rangeTimex) { + rangeTimex = rangeTimex.replace("(", "").replace(")", ""); + String[] components = rangeTimex.split(","); + RangeTimexComponents result = new RangeTimexComponents(); + + if (components.length == 3) { + result.beginTimex = components[0]; + result.endTimex = components[1]; + result.durationTimex = components[2]; + result.isValid = true; + } + + return result; + } + + public static boolean isRangeTimex(String timex) { + return !StringUtility.isNullOrEmpty(timex) && timex.startsWith("("); + } + + public static String setTimexWithContext(String timex, DateContext context) { + return timex.replace(Constants.TimexFuzzyYear, String.format("%04d", context.getYear())); + } + + public static boolean hasDoubleTimex(String comment) { + return comment.equals(Constants.Comment_DoubleTimex); + } + + public static String mergeTimexAlternatives(String timex1, String timex2) { + if (timex1.equals(timex2)) { + return timex1; + } + return timex1 + Constants.CompositeTimexDelimiter + timex2; + } + + public static Map processDoubleTimex(Map resolutionDic, String futureKey, String pastKey, String originTimex) { + String[] timexes = originTimex.split(Constants.CompositeTimexSplit); + + if (!resolutionDic.containsKey(futureKey) || !resolutionDic.containsKey(pastKey) || timexes.length != 2) { + return resolutionDic; + } + + HashMap futureResolution = (HashMap)resolutionDic.get(futureKey); + HashMap pastResolution = (HashMap)resolutionDic.get(pastKey); + futureResolution.put(DateTimeResolutionKey.Timex, timexes[0]); + pastResolution.put(DateTimeResolutionKey.Timex, timexes[1]); + return resolutionDic; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java new file mode 100644 index 000000000..e5a14f78e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java @@ -0,0 +1,87 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; + +import java.util.ArrayList; +import java.util.List; + +public class Token { + private final int start; + private final int end; + private final Metadata metadata; + + public Token(int start, int end, Metadata metadata) { + this.start = start; + this.end = end; + this.metadata = metadata; + } + + public Token(int start, int end) { + this.start = start; + this.end = end; + this.metadata = null; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public int getLength() { + return end < start ? 0 : end - start; + } + + public static List mergeAllTokens(List tokens, String text, String extractorName) { + List result = new ArrayList<>(); + List mergedTokens = new ArrayList<>(); + + tokens.sort((o1, o2) -> { + if (o1.start != o2.start) { + return o1.start - o2.start; + } + + return o2.getLength() - o1.getLength(); + }); + + for (Token token : tokens) { + if (token != null) { + boolean bAdd = true; + for (int i = 0; i < mergedTokens.size() && bAdd; i++) { + // It is included in one of the current tokens + if (token.start >= mergedTokens.get(i).start && token.end <= mergedTokens.get(i).end) { + bAdd = false; + } + + // If it contains overlaps + if (token.start > mergedTokens.get(i).start && token.start < mergedTokens.get(i).end) { + bAdd = false; + } + + // It includes one of the tokens and should replace the included one + if (token.start <= mergedTokens.get(i).start && token.end >= mergedTokens.get(i).end) { + bAdd = false; + mergedTokens.set(i, token); + } + } + + if (bAdd) { + mergedTokens.add(token); + } + } + } + + for (Token token : mergedTokens) { + String substring = text.substring(token.start, token.end); + + ExtractResult er = new ExtractResult(token.start, token.getLength(), substring, extractorName, null, token.metadata); + + result.add(er); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java new file mode 100644 index 000000000..5319445bd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java @@ -0,0 +1,53 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.HashMap; +import java.util.Iterator; + +public class AaNode extends Node { + T word; + int depth; + AaNode parent; + AaNode fail; + + public AaNode(T word, int depth, AaNode parent) { + this.word = word; + this.depth = depth; + this.parent = parent; + this.fail = null; + } + + public AaNode(T word, int depth) { + this(word, depth, null); + } + + public AaNode() { + this(null, 0); + } + + AaNode get(T c) { + return children != null && children.containsKey(c) ? (AaNode)children.get(c) : null; + } + + void put(T c, AaNode value) { + if (children == null) { + children = new HashMap<>(); + } + + children.put(c, value); + } + + @Override + Iterator getEnumerator() { + return children != null ? children.values().stream().map(x -> (AaNode)x).iterator() : null; + } + + @Override + Iterable getIterable() { + return children != null ? (Iterable)children.values().stream().map(x -> (AaNode)x) : null; + } + + @Override + public String toString() { + return word.toString(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java new file mode 100644 index 000000000..6fb2267a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.List; + +public abstract class AbstractMatcher implements IMatcher { + abstract void insert(Iterable value, String id); + + protected void batchInsert(List> values, String[] ids) { + if (values.size() != ids.length) { + throw new IllegalArgumentException(); + } + + for (int i = 0; i < values.size(); i++) { + insert(values.get(i), ids[i]); + } + } + + boolean isMatch(Iterable queryText) { + return find(queryText) != null; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java new file mode 100644 index 000000000..b1c0f054c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java @@ -0,0 +1,89 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.stream.Stream; + +public class AcAutomation extends AbstractMatcher { + protected final AaNode root; + + public AcAutomation() { + root = new AaNode<>(); + } + + @Override + void insert(Iterable value, String id) { + AaNode node = root; + int i = 0; + for (T item : value) { + AaNode child = node.get(item); + if (child == null) { + node.put(item, new AaNode<>(item, i, node)); + child = node.get(item); + } + + node = child; + i++; + } + + node.addValue(id); + } + + @Override + public void init(List> values, String[] ids) { + this.batchInsert(values, ids); + Queue> queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + AaNode node = queue.peek(); + + if (node.children != null) { + for (Object item : node.getIterable()) { + queue.offer((AaNode)item); + } + } + + if (node == root) { + root.fail = root; + continue; + } + + AaNode fail = node.parent.fail; + + while (fail.get(node.word) == null && fail != root) { + fail = fail.fail; + } + + node.fail = fail.get(node.word) != null ? fail.get(node.word) : root; + node.fail = node.fail == node ? root : node.fail; + } + } + + @Override + public Iterable> find(Iterable queryText) { + AaNode node = root; + int i = 0; + List> result = new ArrayList<>(); + + for (T c : queryText) { + while (node.get(c) == null && node != root) { + node = node.fail; + } + + node = node.get(c) == null ? node.get(c) : root; + + for (AaNode t = node; t != root ; t = t.fail) { + if (t.getEnd()) { + result.add(new MatchResult<>(i - t.depth, t.depth + 1, t.values)); + } + } + + i++; + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java new file mode 100644 index 000000000..76b0de58f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.List; + +public interface IMatcher { + void init(List> values, String[] ids); + + Iterable> find(Iterable queryText); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java new file mode 100644 index 000000000..cbdb5c553 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.List; + +public interface ITokenizer { + List tokenize(String input); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java new file mode 100644 index 000000000..6d577691a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java @@ -0,0 +1,66 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.HashSet; +import java.util.Set; + +public class MatchResult { + private int start; + private int length; + private T text; + private Set canonicalValues; + + public MatchResult(int start, int lenght, Set canonicalValues, T text) { + this.start = start; + this.length = lenght; + this.canonicalValues = canonicalValues; + this.text = text; + } + + public MatchResult(int start, int length, Set canonicalValues) { + this(start, length, new HashSet<>(), null); + } + + public MatchResult(int start, int length) { + this(start, length, new HashSet<>()); + } + + public MatchResult() { + this(0, 0); + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public T getText() { + return text; + } + + public void setText(T text) { + this.text = text; + } + + public Set getCanonicalValues() { + return canonicalValues; + } + + public void setCanonicalValues(Set canonicalValues) { + this.canonicalValues = canonicalValues; + } + + public int getEnd() { + return getStart() + getLength(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java new file mode 100644 index 000000000..b6c3b2e11 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.matcher; + +public enum MatchStrategy { + AcAutomaton, + TrieTree +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java new file mode 100644 index 000000000..6be935156 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +public class Node { + Iterator getEnumerator() { + return children != null ? children.values().iterator() : null; + } + + Iterable getIterable() { + return children != null ? children.values() : null; + } + + HashSet values; + Map> children; + + boolean getEnd() { + return this.values != null && !values.isEmpty(); + } + + Node get(T c) { + return children != null && children.containsKey(c) ? children.get(c) : null; + } + + void put(T c, Node value) { + if (children == null) { + children = new HashMap<>(); + } + + children.put(c, value); + } + + void addValue(String value) { + if (values == null) { + values = new HashSet<>(); + } + + values.add(value); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java new file mode 100644 index 000000000..bd3e65437 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java @@ -0,0 +1,86 @@ +package com.microsoft.recognizers.text.matcher; + +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +public class NumberWithUnitTokenizer extends SimpleTokenizer { + private static final HashSet specialTokenCharacters = new HashSet(Arrays.asList('$')); + + /* The main difference between this strategy and SimpleTokenizer is for cases like + * 'Bob's $ 100 cash'. 's' and '$' are independent tokens in SimpleTokenizer. + * However, 's$' will return these two tokens too. Here, we let 's$' be a single + * token to avoid the false positive. + * Besides, letters and digits can't be mixed as a token. For cases like '200ml'. + * '200ml' will be a token in SimpleTokenizer. Here, '200' and 'ml' are independent tokens. + */ + + @Override + public List tokenize(String input) { + List tokens = new ArrayList<>(); + + if (StringUtility.isNullOrEmpty(input)) { + return tokens; + } + + boolean inToken = false; + int tokenStart = 0; + char[] chars = input.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (Character.isWhitespace(c)) { + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + } else if ((!specialTokenCharacters.contains(c) && !Character.isLetterOrDigit(c)) || isChinese(c) || isJapanese(c)) { + // Non-splittable currency units (as "$") are treated as regular letters. For instance, 'us$' should be a single token + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + + tokens.add(new Token(i, 1, input.substring(i, i + 1))); + } else { + if (inToken && i > 0) { + char preChar = chars[i - 1]; + if (isSplittableUnit(c, preChar)) { + // Split if letters or non-splittable units are adjacent with digits. + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + tokenStart = i; + } + } + + if (!inToken) { + tokenStart = i; + inToken = true; + } + } + } + + if (inToken) { + tokens.add(new Token(tokenStart, chars.length - tokenStart, input.substring(tokenStart, chars.length))); + } + + return tokens; + } + + private boolean isSplittableUnit(char curChar, char preChar) { + // To handle cases like '200ml', digits and letters cannot be mixed as a single token. '200ml' will be tokenized to '200' and 'ml'. + if ((Character.isLetter(curChar) && Character.isDigit(preChar)) || (Character.isDigit(curChar) && Character.isLetter(preChar))) { + return true; + } + + // Non-splittable currency units can't be mixed with digits. For example, '$100' or '100$' will be tokenized to '$' and '100', + // '1$50' will be tokenized to '1', '$', and '50' + if ((Character.isDigit(curChar) && specialTokenCharacters.contains(preChar)) || (specialTokenCharacters.contains(curChar) && Character.isDigit(preChar))) { + return true; + } + + // Non-splittable currency units adjacent with letters are treated as regular token characters. For instance, 's$' or 'u$d' are single tokens. + return false; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java new file mode 100644 index 000000000..7d836e394 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java @@ -0,0 +1,78 @@ +package com.microsoft.recognizers.text.matcher; + +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleTokenizer implements ITokenizer { + @Override + public List tokenize(String input) { + List tokens = new ArrayList<>(); + + if (StringUtility.isNullOrEmpty(input)) { + return tokens; + } + + boolean inToken = false; + int tokenStart = 0; + char[] chars = input.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + + if (Character.isWhitespace(c)) { + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + } else if (!Character.isLetterOrDigit(c) || isCjk(c)) { + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + + tokens.add(new Token(i, 1, input.substring(i, i + 1))); + } else { + if (!inToken) { + tokenStart = i; + inToken = true; + } + } + } + + if (inToken) { + tokens.add(new Token(tokenStart, chars.length - tokenStart, input.substring(tokenStart))); + } + + return tokens; + } + + protected boolean isChinese(char c) { + int uc = (int)c; + + return (uc >= (int)0x4E00 && uc <= (int)0x9FBF) || (uc >= (int)0x3400 && uc <= (int)0x4DBF); + } + + protected boolean isJapanese(char c) { + int uc = (int)c; + + return (uc >= 0x3040 && uc <= 0x309F) || + (uc >= 0x30A0 && uc <= (int)0x30FF) || + (uc >= (int)0xFF66 && uc <= (int)0xFF9D); + } + + protected boolean isKorean(char c) { + int uc = (int)c; + + return (uc >= (int)0xAC00 && uc <= (int)0xD7AF) || + (uc >= (int)0x1100 && uc <= (int)0x11FF) || + (uc >= (int)0x3130 && uc <= (int)0x318F) || + (uc >= (int)0xFFB0 && uc <= (int)0xFFDC); + } + + // Check the character is Chinese/Japanese/Korean. + // For those languages which are not using whitespace delimited symbol, we only simply tokenize the sentence by each single character. + private boolean isCjk(char c) { + return isChinese(c) || isJapanese(c) || isKorean(c); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java new file mode 100644 index 000000000..10d651d4d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java @@ -0,0 +1,99 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class StringMatcher { + private final ITokenizer tokenizer; + private final IMatcher matcher; + + public StringMatcher(MatchStrategy strategy, ITokenizer tokenizer) { + this.tokenizer = tokenizer != null ? tokenizer : new SimpleTokenizer(); + switch (strategy) { + case AcAutomaton: + matcher = new AcAutomation<>(); + break; + case TrieTree: + matcher = new TrieTree<>(); + break; + default: + throw new IllegalArgumentException(); + } + } + + public StringMatcher(MatchStrategy strategy) { + this(strategy, null); + } + + public StringMatcher(ITokenizer tokenizer) { + this(MatchStrategy.TrieTree, tokenizer); + } + + public StringMatcher() { + this(MatchStrategy.TrieTree, null); + } + + public void init(Iterable values) { + init(values, StreamSupport.stream(values.spliterator(), false).toArray(size -> new String[size])); + } + + void init(Iterable values, String[] ids) { + List> tokenizedValues = getTokenizedText(values); + init(tokenizedValues, ids); + } + + void init(Map> valuesMap) { + ArrayList values = new ArrayList<>(); + ArrayList ids = new ArrayList<>(); + + for (Map.Entry> item : valuesMap.entrySet()) { + String id = item.getKey(); + for (String value : item.getValue()) { + values.add(value); + ids.add(id); + } + } + + List> tokenizedValues = getTokenizedText(values); + init(tokenizedValues, ids.toArray(new String[0])); + } + + void init(List> tokenizedValues, String[] ids) { + matcher.init(tokenizedValues, ids); + } + + private List> getTokenizedText(Iterable values) { + List> result = new ArrayList<>(); + for (String value: values) { + result.add(tokenizer.tokenize(value).stream().map(i -> i.text).collect(Collectors.toCollection(ArrayList::new))); + } + + return result; + } + + public Iterable> find(Iterable tokenizedQuery) { + return matcher.find(tokenizedQuery); + } + + public Iterable> find(String queryText) { + List queryTokens = tokenizer.tokenize(queryText); + Iterable tokenizedQueryText = queryTokens.stream().map(t -> t.text).collect(Collectors.toCollection(ArrayList::new)); + + List> result = new ArrayList<>(); + for (MatchResult r : find(tokenizedQueryText)) { + Token startToken = queryTokens.get(r.getStart()); + Token endToken = queryTokens.get(r.getStart() + r.getLength() - 1); + int start = startToken.getStart(); + int length = endToken.getEnd() - startToken.getStart(); + String rtext = queryText.substring(start, start + length); + + result.add(new MatchResult(start, length, r.getCanonicalValues(), rtext)); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java new file mode 100644 index 000000000..d7ca62808 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java @@ -0,0 +1,30 @@ +package com.microsoft.recognizers.text.matcher; + +public class Token { + private final int start; + private final int length; + + String text; + + public Token(int start, int length, String text) { + this.start = start; + this.length = length; + this.text = text; + } + + public Token(int start, int length) { + this(start, length, null); + } + + int getStart() { + return start; + } + + int getLength() { + return length; + } + + int getEnd() { + return start + length; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java new file mode 100644 index 000000000..adc663350 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java @@ -0,0 +1,67 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +public class TrieTree extends AbstractMatcher { + protected final Node root; + + public TrieTree() { + root = new Node<>(); + } + + @Override + void insert(Iterable value, String id) { + Node node = root; + + for (T item : value) { + Node child = node.get(item); + + if (child == null) { + node.put(item, new Node<>()); + child = node.get(item); + } + + node = child; + } + + node.addValue(id); + } + + @Override + public void init(List> values, String[] ids) { + batchInsert(values, ids); + } + + @Override + public Iterable> find(Iterable queryText) { + List> result = new ArrayList<>(); + + ArrayList queryArray = new ArrayList<>(); + queryText.iterator().forEachRemaining(queryArray::add); + + for (int i = 0; i < queryArray.size(); i++) { + Node node = root; + for (int j = i; j <= queryArray.size(); j++) { + if (node.getEnd()) { + result.add(new MatchResult<>(i, j - i, node.values)); + } + + if (j == queryArray.size()) { + break; + } + + T text = queryArray.get(j); + if (node.get(text) == null) { + break; + } + + node = node.get(text); + } + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java new file mode 100644 index 000000000..1f4458502 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.number; + +public class Constants { + public static final String SYS_NUM_CARDINAL = "builtin.num.cardinal"; + public static final String SYS_NUM_DOUBLE = "builtin.num.double"; + public static final String SYS_NUM_FRACTION = "builtin.num.fraction"; + public static final String SYS_NUM_INTEGER = "builtin.num.integer"; + public static final String SYS_NUM = "builtin.num"; + public static final String SYS_NUM_ORDINAL = "builtin.num.ordinal"; + public static final String SYS_NUM_PERCENTAGE = "builtin.num.percentage"; + public static final String SYS_NUMRANGE = "builtin.num.numberrange"; + + // Model type name + public static final String MODEL_NUMBER = "number"; + public static final String MODEL_NUMBERRANGE = "numberrange"; + public static final String MODEL_ORDINAL = "ordinal"; + public static final String MODEL_PERCENTAGE = "percentage"; + + // NARROW NO-BREAK SPACE + public static final char NO_BREAK_SPACE = '\u202f'; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java new file mode 100644 index 000000000..d801a8236 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java @@ -0,0 +1,53 @@ +package com.microsoft.recognizers.text.number; + +public class LongFormatType { + + // Reference Value : 1234567.89 + + // 1,234,567 + public static LongFormatType IntegerNumComma = new LongFormatType(',', '\0'); + + // 1.234.567 + public static LongFormatType IntegerNumDot = new LongFormatType('.', '\0'); + + // 1 234 567 + public static LongFormatType IntegerNumBlank = new LongFormatType(' ', '\0'); + + // 1 234 567 + public static LongFormatType IntegerNumNoBreakSpace = new LongFormatType(Constants.NO_BREAK_SPACE, '\0'); + + // 1'234'567 + public static LongFormatType IntegerNumQuote = new LongFormatType('\'', '\0'); + + // 1,234,567.89 + public static LongFormatType DoubleNumCommaDot = new LongFormatType(',', '.'); + + // 1,234,567·89 + public static LongFormatType DoubleNumCommaCdot = new LongFormatType(',', '·'); + + // 1 234 567,89 + public static LongFormatType DoubleNumBlankComma = new LongFormatType(' ', ','); + + // 1 234 567,89 + public static LongFormatType DoubleNumNoBreakSpaceComma = new LongFormatType(Constants.NO_BREAK_SPACE, ','); + + // 1 234 567.89 + public static LongFormatType DoubleNumBlankDot = new LongFormatType(' ', '.'); + + // 1 234 567.89 + public static LongFormatType DoubleNumNoBreakSpaceDot = new LongFormatType(Constants.NO_BREAK_SPACE, '.'); + + // 1.234.567,89 + public static LongFormatType DoubleNumDotComma = new LongFormatType('.', ','); + + // 1'234'567,89 + public static LongFormatType DoubleNumQuoteComma = new LongFormatType('\'', ','); + + public final char decimalsMark; + public final char thousandsMark; + + public LongFormatType(char thousandsMark, char decimalsMark) { + this.thousandsMark = thousandsMark; + this.decimalsMark = decimalsMark; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java new file mode 100644 index 000000000..08fac2c35 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.number; + +public enum NumberMode { + //Default is for unit and datetime + Default, + //Add 67.5 billion & million support. + Currency, + //Don't extract number from cases like 16ml + PureNumber, + // Unit is for unit + Unit +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java new file mode 100644 index 000000000..e3f2e6cfa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java @@ -0,0 +1,16 @@ +package com.microsoft.recognizers.text.number; + +public enum NumberOptions { + None(0), + PercentageMode(1); + + private final int value; + + NumberOptions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java new file mode 100644 index 000000000..d2af53c68 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.number; + +public abstract class NumberRangeConstants { + // Number range regex type + public static final String TWONUM = "TwoNum"; + public static final String TWONUMBETWEEN = "TwoNumBetween"; + public static final String TWONUMTILL = "TwoNumTill"; + public static final String MORE = "More"; + public static final String LESS = "Less"; + public static final String EQUAL = "Equal"; + + // Brackets and comma for number range resolution value + public static final char LEFT_OPEN = '('; + public static final char RIGHT_OPEN = ')'; + public static final char LEFT_CLOSED = '['; + public static final char RIGHT_CLOSED = ']'; + public static final char INTERVAL_SEPARATOR = ','; + + // Invalid number + public static final int INVALID_NUM = -1; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java new file mode 100644 index 000000000..2b76f483b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java @@ -0,0 +1,217 @@ +package com.microsoft.recognizers.text.number; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.german.parsers.GermanNumberParserConfiguration; +import com.microsoft.recognizers.text.number.models.NumberModel; +import com.microsoft.recognizers.text.number.models.NumberRangeModel; +import com.microsoft.recognizers.text.number.models.OrdinalModel; +import com.microsoft.recognizers.text.number.models.PercentModel; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.number.parsers.BaseNumberRangeParser; +import com.microsoft.recognizers.text.number.portuguese.parsers.PortugueseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; + +import java.util.List; +import java.util.function.Function; + +public class NumberRecognizer extends Recognizer { + + public NumberRecognizer() { + this(null, NumberOptions.None, true); + } + + public NumberRecognizer(String culture) { + this(culture, NumberOptions.None, false); + } + + public NumberRecognizer(NumberOptions numberOptions) { + this(null, numberOptions, true); + } + + public NumberRecognizer(NumberOptions numberOptions, boolean lazyInitialization) { + this(null, numberOptions, lazyInitialization); + } + + public NumberRecognizer(String culture, NumberOptions numberOptions, boolean lazyInitialization) { + super(culture, numberOptions, lazyInitialization); + } + + //region Helper methods for less verbosity + public NumberModel getNumberModel() { + return getNumberModel(null, true); + } + + public NumberModel getNumberModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(NumberModel.class, culture, fallbackToDefaultCulture); + } + + public OrdinalModel getOrdinalModel() { + return getOrdinalModel(null, true); + } + + public OrdinalModel getOrdinalModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(OrdinalModel.class, culture, fallbackToDefaultCulture); + } + + public PercentModel getPercentageModel() { + return getPercentageModel(null, true); + } + + public PercentModel getPercentageModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(PercentModel.class, culture, fallbackToDefaultCulture); + } + + public NumberRangeModel getNumberRangeModel() { + return getNumberRangeModel(null, true); + } + + public NumberRangeModel getNumberRangeModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(NumberRangeModel.class, culture, fallbackToDefaultCulture); + } + + public static List recognizeNumber(String query, String culture) { + return recognizeNumber(query, culture, NumberOptions.None, true); + } + + public static List recognizeNumber(String query, String culture, NumberOptions options) { + return recognizeNumber(query, culture, options, true); + } + + public static List recognizeNumber(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getNumberModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeOrdinal(String query, String culture) { + return recognizeOrdinal(query, culture, NumberOptions.None, true); + } + + public static List recognizeOrdinal(String query, String culture, NumberOptions options) { + return recognizeOrdinal(query, culture, options, true); + } + + public static List recognizeOrdinal(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getOrdinalModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizePercentage(String query, String culture) { + return recognizePercentage(query, culture, NumberOptions.None, true); + } + + public static List recognizePercentage(String query, String culture, NumberOptions options) { + return recognizePercentage(query, culture, options, true); + } + + public static List recognizePercentage(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getPercentageModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeNumberRange(String query, String culture) { + return recognizeNumberRange(query, culture, NumberOptions.None, true); + } + + public static List recognizeNumberRange(String query, String culture, NumberOptions options) { + return recognizeNumberRange(query, culture, options, true); + } + + public static List recognizeNumberRange(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getNumberRangeModel(culture, fallbackToDefaultCulture), query, options); + } + //endregion + + private static List recognizeByModel(Function getModelFun, String query, NumberOptions options) { + NumberRecognizer recognizer = new NumberRecognizer(options); + IModel model = getModelFun.apply(recognizer); + return model.parse(query); + } + + @Override + protected void initializeConfiguration() { + //region English + registerModel(NumberModel.class, Culture.English, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new EnglishNumberParserConfiguration(options)), + com.microsoft.recognizers.text.number.english.extractors.NumberExtractor.getInstance(NumberMode.PureNumber, options))); + registerModel(OrdinalModel.class, Culture.English, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new EnglishNumberParserConfiguration(options)), + com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor.getInstance())); + registerModel(PercentModel.class, Culture.English, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new EnglishNumberParserConfiguration(options)), + new com.microsoft.recognizers.text.number.english.extractors.PercentageExtractor(options))); + registerModel(NumberRangeModel.class, Culture.English, (options) -> new NumberRangeModel( + new BaseNumberRangeParser(new EnglishNumberRangeParserConfiguration()), + new com.microsoft.recognizers.text.number.english.extractors.NumberRangeExtractor())); + + //endregion + + //region Spanish + registerModel(NumberModel.class, Culture.Spanish, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new SpanishNumberParserConfiguration()), + com.microsoft.recognizers.text.number.spanish.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.Spanish, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new SpanishNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.Spanish, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new SpanishNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.spanish.extractors.PercentageExtractor())); + //endregion + + //region Portuguese + registerModel(NumberModel.class, Culture.Portuguese, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new PortugueseNumberParserConfiguration()), + com.microsoft.recognizers.text.number.portuguese.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.Portuguese, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new PortugueseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.portuguese.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.Portuguese, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new PortugueseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.portuguese.extractors.PercentageExtractor())); + //endregion + + //region French + registerModel(NumberModel.class, Culture.French, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new FrenchNumberParserConfiguration()), + com.microsoft.recognizers.text.number.french.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.French, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new FrenchNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.French, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new FrenchNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.french.extractors.PercentageExtractor())); + //endregion + + //region German + registerModel(NumberModel.class, Culture.German, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new GermanNumberParserConfiguration()), + com.microsoft.recognizers.text.number.german.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.German, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new GermanNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.german.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.German, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new GermanNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.german.extractors.PercentageExtractor())); + //endregion + + //region Chinese + registerModel(NumberModel.class, Culture.Chinese, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new ChineseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor())); + registerModel(OrdinalModel.class, Culture.Chinese, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new ChineseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.Chinese, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new ChineseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.PercentageExtractor())); + registerModel(NumberRangeModel.class, Culture.Chinese, (options) -> new NumberRangeModel( + new BaseNumberRangeParser(new ChineseNumberRangeParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.NumberRangeExtractor())); + //endregion + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java new file mode 100644 index 000000000..4ca896d2b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.number.chinese; + +/** + * These modes only apply to ChineseNumberExtractor. + * The default more urilizes an allow list to avoid extracting numbers in ambiguous/undesired combinations of Chinese ideograms. + * ExtractAll mode is to be used in cases where extraction should be more aggressive (e.g. in Units extraction). + */ +public enum ChineseNumberExtractorMode { + /** + * Number extraction with an allow list that filters what numbers to extract. + */ + Default, + + /** + * Extract all number-related terms aggressively. + */ + ExtractAll +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java new file mode 100644 index 000000000..016fdc02c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + public CardinalExtractor() { + this(ChineseNumberExtractorMode.Default); + } + + public CardinalExtractor(ChineseNumberExtractorMode mode) { + HashMap builder = new HashMap<>(); + + IntegerExtractor intExtractChs = new IntegerExtractor(mode); + builder.putAll(intExtractChs.getRegexes()); + + DoubleExtractor douExtractorChs = new DoubleExtractor(); + builder.putAll(douExtractorChs.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java new file mode 100644 index 000000000..7f6f2c642 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleSpecialsChars, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + // (-)2.5, can avoid cases like ip address xx.xx.xx.xx + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleSpecialsCharsWithNegatives, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + //(-).2 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleDoubleSpecialsChars, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + // 1.0 K + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + //15.2万 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleWithThousandsRegex, Pattern.UNICODE_CHARACTER_CLASS), "Double" + ChineseNumeric.LangMarker); + //四十五点三三 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "Double" + ChineseNumeric.LangMarker); + // 2e6, 21.2e0 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + //2^5 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleScientificNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java new file mode 100644 index 000000000..f6b1223c3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor() { + HashMap builder = new HashMap<>(); + + // -4 5/2, 4 6/3 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionNotationSpecialsCharsRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + // 8/3 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + //四分之六十五 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.AllFractionNumber, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + ChineseNumeric.LangMarker); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java new file mode 100644 index 000000000..4217df048 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java @@ -0,0 +1,66 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import static com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode.Default; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(Default); + } + + public IntegerExtractor(ChineseNumberExtractorMode mode) { + HashMap builder = new HashMap<>(); + + // 123456, -123456 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersSpecialsChars, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + //15k, 16 G + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersSpecialsCharsWithSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + //1,234, 2,332,111 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DottedNumbersSpecialsChar, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + //半百 半打 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersWithHalfDozen, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + //一打 五十打 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersWithDozen, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + + switch (mode) { + case Default: + // 一百五十五, 负一亿三百二十二. + // Uses an allow list to avoid extracting "四" from "四川" + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.NumbersWithAllowListRegex, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + break; + + case ExtractAll: + // 一百五十五, 负一亿三百二十二, "四" from "四川". + // Uses no allow lists and extracts all potential integers (useful in Units, for example). + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.NumbersAggressiveRegex, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + break; + + default: + break; + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java new file mode 100644 index 000000000..b61bd980c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java @@ -0,0 +1,63 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + public NumberExtractor() { + this(ChineseNumberExtractorMode.Default); + } + + public NumberExtractor(ChineseNumberExtractorMode mode) { + + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardExtractChs = new CardinalExtractor(mode); + builder.putAll(cardExtractChs.getRegexes()); + + // Add Fraction + FractionExtractor fracExtractChs = new FractionExtractor(); + builder.putAll(fracExtractChs.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + + for (Map.Entry pair : ChineseNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java new file mode 100644 index 000000000..d1cc5725d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java @@ -0,0 +1,78 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberRangeConstants; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.extractors.BaseNumberRangeExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseCJKNumberParser; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NumberRangeExtractor extends BaseNumberRangeExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUMRANGE; + } + + public NumberRangeExtractor() { + this(ChineseNumberExtractorMode.Default); + } + + public NumberRangeExtractor(ChineseNumberExtractorMode mode) { + + super(new NumberExtractor(), new OrdinalExtractor(), new BaseCJKNumberParser(new ChineseNumberParserConfiguration())); + + HashMap builder = new HashMap<>(); + + // 在...和...之间 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + NumberRangeConstants.TWONUMBETWEEN); + + // 大于...小于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + + // 小于...大于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + + // ...到/至..., 20~30 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex4, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUMTILL); + + // 大于/多于/高于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeMoreRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + + // 比...大/高/多 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeMoreRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + + // ...多/以上/之上 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeMoreRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + + // 小于/少于/低于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeLessRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + + // 比...小/低/少 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeLessRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + + // .../以下/之下 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeLessRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + + // 等于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeEqualRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.EQUAL); + + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..2bcd86e93 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + //第一百五十四 + builder.put(Pattern.compile(ChineseNumeric.OrdinalRegex, Pattern.UNICODE_CHARACTER_CLASS), "Ordinal" + ChineseNumeric.LangMarker); + + //第2565, 第1234 + builder.put(Pattern.compile(ChineseNumeric.OrdinalNumbersRegex, Pattern.UNICODE_CHARACTER_CLASS), "Ordinal" + ChineseNumeric.LangMarker); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java new file mode 100644 index 000000000..a3381f574 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java @@ -0,0 +1,99 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_PERCENTAGE; + } + + public PercentageExtractor() { + HashMap builder = new HashMap<>(); + + //二十个百分点, 四点五个百分点 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.PercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "Per" + ChineseNumeric.LangMarker); + + //百分之五十 百分之一点五 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimplePercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "Per" + ChineseNumeric.LangMarker); + + //百分之56.2 百分之12 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersPercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之3,000 百分之1,123 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersPercentageWithSeparatorRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之3.2 k + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12.56个百分点 0.4个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionPercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //15,123个百分点 111,111个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionPercentageWithSeparatorRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12.1k个百分点 15.1k个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之22 百分之120 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleNumbersPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之15k + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleNumbersPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之1,111 百分之9,999 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleNumbersPercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.IntegerPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12k个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.IntegerPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //2,123个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersFractionPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //32.5% + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleIntegerPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //2折 2.5折 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersFoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //三折 六点五折 七五折 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //5成 6成半 6成4 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleFoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //七成半 七成五 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SpecialsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //2成 2.5成 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersSpecialsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //三成 六点五成 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleSpecialsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //打对折 半成 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SpecialsFoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java new file mode 100644 index 000000000..2a89909c1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java @@ -0,0 +1,82 @@ +package com.microsoft.recognizers.text.number.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseCJKNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.TreeMap; +import java.util.regex.Pattern; + +public class ChineseNumberParserConfiguration extends BaseCJKNumberParserConfiguration { + + public ChineseNumberParserConfiguration() { + super( + ChineseNumeric.LangMarker, + new CultureInfo(Culture.Chinese), + ChineseNumeric.CompoundNumberLanguage, + ChineseNumeric.MultiDecimalSeparatorCulture, + NumberOptions.None, + ChineseNumeric.NonDecimalSeparatorChar, + ChineseNumeric.DecimalSeparatorChar, + ChineseNumeric.FractionMarkerToken, + ChineseNumeric.HalfADozenText, + ChineseNumeric.WordSeparatorToken, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyMap(), + Collections.emptyMap(), + ChineseNumeric.RoundNumberMap, + null, + Pattern.compile(ChineseNumeric.DigitalNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.NegativeNumberTermsRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + null, + ChineseNumeric.ZeroToNineMap, + ChineseNumeric.RoundNumberMapChar, + ChineseNumeric.FullToHalfMap, + new TreeMap(new Comparator() { + @Override + public int compare(String a, String b) { + return a.length() > b.length() ? 1 : -1; + } + }) { + { + putAll(ChineseNumeric.UnitMap); + } + }, + ChineseNumeric.TratoSimMap, + ChineseNumeric.RoundDirectList, + Pattern.compile(ChineseNumeric.FracSplitRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.DigitNumRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.SpeGetNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeRegExp(ChineseNumeric.PercentageRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.PointRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.DoubleAndRoundRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.PairRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.DozenRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.RoundNumberIntegerRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + ChineseNumeric.ZeroChar, + ChineseNumeric.TenChars, + ChineseNumeric.PairChar, + RegExpUtility.getSafeRegExp(ChineseNumeric.PercentageNumRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS) + ); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + return tokens; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + return 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java new file mode 100644 index 000000000..3671a4767 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java @@ -0,0 +1,96 @@ +package com.microsoft.recognizers.text.number.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.chinese.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseCJKNumberParser; +import com.microsoft.recognizers.text.number.parsers.INumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; + +import java.util.regex.Pattern; + +public class ChineseNumberRangeParserConfiguration implements INumberRangeParserConfiguration { + + public final CultureInfo cultureInfo; + public final IExtractor numberExtractor; + public final IExtractor ordinalExtractor; + public final IParser numberParser; + public final Pattern moreOrEqual; + public final Pattern lessOrEqual; + public final Pattern moreOrEqualSuffix; + public final Pattern lessOrEqualSuffix; + public final Pattern moreOrEqualSeparate; + public final Pattern lessOrEqualSeparate; + + public ChineseNumberRangeParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public ChineseNumberRangeParserConfiguration(CultureInfo ci) { + this.cultureInfo = ci + ; + this.numberExtractor = new NumberExtractor(); + this.ordinalExtractor = new OrdinalExtractor(); + this.numberParser = new BaseCJKNumberParser(new ChineseNumberParserConfiguration()); + + this.moreOrEqual = Pattern.compile(ChineseNumeric.MoreOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqual = Pattern.compile(ChineseNumeric.LessOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSuffix = Pattern.compile(ChineseNumeric.MoreOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSuffix = Pattern.compile(ChineseNumeric.LessOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSeparate = Pattern.compile(ChineseNumeric.OneNumberRangeMoreSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSeparate = Pattern.compile(ChineseNumeric.OneNumberRangeLessSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public IExtractor getNumberExtractor() { + return this.numberExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return this.ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return this.numberParser; + } + + @Override + public Pattern getMoreOrEqual() { + return this.moreOrEqual; + } + + @Override + public Pattern getLessOrEqual() { + return this.lessOrEqual; + } + + @Override + public Pattern getMoreOrEqualSuffix() { + return this.moreOrEqualSuffix; + } + + @Override + public Pattern getLessOrEqualSuffix() { + return this.lessOrEqualSuffix; + } + + @Override + public Pattern getMoreOrEqualSeparate() { + return this.moreOrEqualSeparate; + } + + @Override + public Pattern getLessOrEqualSeparate() { + return this.lessOrEqualSeparate; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java new file mode 100644 index 000000000..33b921949 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java @@ -0,0 +1,66 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(EnglishNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor() { + this(EnglishNumeric.PlaceHolderDefault); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.putAll(IntegerExtractor.getInstance(placeholder).getRegexes()); + builder.putAll(DoubleExtractor.getInstance(placeholder).getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java new file mode 100644 index 000000000..16cb600c3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java @@ -0,0 +1,78 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static DoubleExtractor getInstance() { + return getInstance(EnglishNumeric.PlaceHolderDefault); + } + + public static DoubleExtractor getInstance(String placeholder) { + + if (!instances.containsKey(placeholder)) { + DoubleExtractor instance = new DoubleExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private DoubleExtractor() { + this(EnglishNumeric.PlaceHolderDefault); + } + + private DoubleExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(Pattern.compile(EnglishNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(Pattern.compile(EnglishNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleEng"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumCommaDot, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumBlankDot, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceDot, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java new file mode 100644 index 000000000..23fb2f9c8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java @@ -0,0 +1,74 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final NumberOptions options; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap, FractionExtractor> instances = new ConcurrentHashMap<>(); + + public static FractionExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + FractionExtractor instance = new FractionExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private FractionExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + + HashMap builder = new HashMap<>(); + + builder.put(Pattern.compile(EnglishNumeric.FractionNotationWithSpacesRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(Pattern.compile(EnglishNumeric.FractionNotationRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(Pattern.compile(EnglishNumeric.FractionNounRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + builder.put(Pattern.compile(EnglishNumeric.FractionNounWithArticleRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + + if (mode != NumberMode.Unit) { + if ((options.ordinal() & NumberOptions.PercentageMode.ordinal()) != 0) { + builder.put(Pattern.compile(EnglishNumeric.FractionPrepositionWithinPercentModeRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + } else { + builder.put(Pattern.compile(EnglishNumeric.FractionPrepositionRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + } + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java new file mode 100644 index 000000000..fb21cc9ce --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java @@ -0,0 +1,75 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static IntegerExtractor getInstance() { + return getInstance(EnglishNumeric.PlaceHolderDefault); + } + + public static IntegerExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + IntegerExtractor instance = new IntegerExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private IntegerExtractor() { + this(EnglishNumeric.PlaceHolderDefault); + } + + private IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(Pattern.compile(EnglishNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(Pattern.compile(EnglishNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerEng"); + builder.put(Pattern.compile(EnglishNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerEng"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumComma, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java new file mode 100644 index 000000000..7a4179b59 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java @@ -0,0 +1,116 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + private final NumberOptions options; + private final Pattern negativeNumberTermsRegex; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.of(this.negativeNumberTermsRegex); + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + + private NumberExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + this.negativeNumberTermsRegex = Pattern.compile(EnglishNumeric.NegativeNumberTermsRegex + '$', Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardinalExtractor = null; + switch (mode) { + case PureNumber: + cardinalExtractor = CardinalExtractor.getInstance(EnglishNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardinalExtractor == null) { + cardinalExtractor = CardinalExtractor.getInstance(); + } + + builder.putAll(cardinalExtractor.getRegexes()); + + // Add Fraction + FractionExtractor fractionExtractor = FractionExtractor.getInstance(mode, this.options); + builder.putAll(fractionExtractor.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : EnglishNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java new file mode 100644 index 000000000..49f515883 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.NumberRangeConstants; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.extractors.BaseNumberRangeExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NumberRangeExtractor extends BaseNumberRangeExtractor { + + private final Map regexes; + + public NumberRangeExtractor() { + super(NumberExtractor.getInstance(), OrdinalExtractor.getInstance(), new BaseNumberParser(new EnglishNumberParserConfiguration())); + + HashMap builder = new HashMap<>(); + + // between...and... + builder.put(Pattern.compile(EnglishNumeric.TwoNumberRangeRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUMBETWEEN); + // more than ... less than ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.TwoNumberRangeRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + // less than ... more than ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.TwoNumberRangeRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + // from ... to/~/- ... + builder.put(Pattern.compile(EnglishNumeric.TwoNumberRangeRegex4, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUMTILL); + // more/greater/higher than ... + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeMoreRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + // 30 and/or greater/higher + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeMoreRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + // less/smaller/lower than ... + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeLessRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + // 30 and/or less/smaller/lower + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeLessRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + // equal to ... + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeEqualRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.EQUAL); + // equal to 30 or more than, larger than 30 or equal to ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeMoreSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + NumberRangeConstants.MORE); + // equal to 30 or less, smaller than 30 or equal ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeLessSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + NumberRangeConstants.LESS); + + this.regexes = Collections.unmodifiableMap(builder); + } + + @Override + protected Map getRegexes() { + return this.regexes; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..6c0979315 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java @@ -0,0 +1,65 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static OrdinalExtractor getInstance() { + return getInstance(""); + } + + private static OrdinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + OrdinalExtractor instance = new OrdinalExtractor(); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(Pattern.compile(EnglishNumeric.OrdinalSuffixRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(Pattern.compile(EnglishNumeric.OrdinalNumericRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(Pattern.compile(EnglishNumeric.OrdinalEnglishRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdEng"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.OrdinalRoundNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdEng"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java new file mode 100644 index 000000000..1799b73c1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + public NumberOptions getOptions() { + return options; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(NumberMode.Default, options)); + + this.options = options; + + Set builder = new HashSet<>(); + builder.add(EnglishNumeric.NumberWithSuffixPercentage); + builder.add(EnglishNumeric.NumberWithPrefixPercentage); + + if ((options.ordinal() & NumberOptions.PercentageMode.ordinal()) != 0) { + builder.add(EnglishNumeric.FractionNumberWithSuffixPercentage); + builder.add(EnglishNumeric.NumberWithPrepositionPercentage); + } + + this.regexes = buildRegexes(builder); + } + + @Override + protected Set getRegexes() { + return this.regexes; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java new file mode 100644 index 000000000..67a9beb76 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java @@ -0,0 +1,105 @@ +package com.microsoft.recognizers.text.number.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class EnglishNumberParserConfiguration extends BaseNumberParserConfiguration { + + public EnglishNumberParserConfiguration() { + this(NumberOptions.None); + } + + public EnglishNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.English), options); + } + + public EnglishNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + EnglishNumeric.LangMarker, + cultureInfo, + EnglishNumeric.CompoundNumberLanguage, + EnglishNumeric.MultiDecimalSeparatorCulture, + options, + EnglishNumeric.NonDecimalSeparatorChar, + EnglishNumeric.DecimalSeparatorChar, + EnglishNumeric.FractionMarkerToken, + EnglishNumeric.HalfADozenText, + EnglishNumeric.WordSeparatorToken, + EnglishNumeric.WrittenDecimalSeparatorTexts, + EnglishNumeric.WrittenGroupSeparatorTexts, + EnglishNumeric.WrittenIntegerSeparatorTexts, + EnglishNumeric.WrittenFractionSeparatorTexts, + EnglishNumeric.CardinalNumberMap, + EnglishNumeric.OrdinalNumberMap, + EnglishNumeric.RoundNumberMap, + Pattern.compile(EnglishNumeric.HalfADozenRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(EnglishNumeric.DigitalNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(EnglishNumeric.NegativeNumberSignRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(EnglishNumeric.FractionPrepositionRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS)); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + List words = new ArrayList<>(); + + for (int i = 0; i < tokens.size(); i++) { + if (tokens.get(i).contains("-")) { + String[] splitTokens = tokens.get(i).split(Pattern.quote("-")); + if (splitTokens.length == 2 && getOrdinalNumberMap().containsKey(splitTokens[1])) { + words.add(splitTokens[0]); + words.add(splitTokens[1]); + } else { + words.add(tokens.get(i)); + } + } else if (i < tokens.size() - 2 && tokens.get(i + 1).equals("-")) { + if (getOrdinalNumberMap().containsKey(tokens.get(i + 2))) { + words.add(tokens.get(i)); + words.add(tokens.get(i + 2)); + } else { + words.add(tokens.get(i) + tokens.get(i + 1) + tokens.get(i + 2)); + } + + i += 2; + } else { + words.add(tokens.get(i)); + } + } + + return words; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + if (numberStr.contains("-")) { + String[] numbers = numberStr.split(Pattern.quote("-")); + long ret = 0; + for (String number : numbers) { + if (getOrdinalNumberMap().containsKey(number)) { + ret += getOrdinalNumberMap().get(number); + } else if (getCardinalNumberMap().containsKey(number)) { + ret += getCardinalNumberMap().get(number); + } + } + + return ret; + } + + if (getOrdinalNumberMap().containsKey(numberStr)) { + return getOrdinalNumberMap().get(numberStr); + } + + if (getCardinalNumberMap().containsKey(numberStr)) { + return getCardinalNumberMap().get(numberStr); + } + + return 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java new file mode 100644 index 000000000..4a0706885 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java @@ -0,0 +1,97 @@ +package com.microsoft.recognizers.text.number.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.english.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.parsers.INumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishNumberRangeParserConfiguration implements INumberRangeParserConfiguration { + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public IExtractor getNumberExtractor() { + return this.numberExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return this.ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return this.numberParser; + } + + @Override + public Pattern getMoreOrEqual() { + return this.moreOrEqual; + } + + @Override + public Pattern getLessOrEqual() { + return this.lessOrEqual; + } + + @Override + public Pattern getMoreOrEqualSuffix() { + return this.moreOrEqualSuffix; + } + + @Override + public Pattern getLessOrEqualSuffix() { + return this.lessOrEqualSuffix; + } + + @Override + public Pattern getMoreOrEqualSeparate() { + return this.moreOrEqualSeparate; + } + + @Override + public Pattern getLessOrEqualSeparate() { + return this.lessOrEqualSeparate; + } + + private final CultureInfo cultureInfo; + private final IExtractor numberExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final Pattern moreOrEqual; + private final Pattern lessOrEqual; + private final Pattern moreOrEqualSuffix; + private final Pattern lessOrEqualSuffix; + private final Pattern moreOrEqualSeparate; + private final Pattern lessOrEqualSeparate; + + public EnglishNumberRangeParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public EnglishNumberRangeParserConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.numberExtractor = NumberExtractor.getInstance(); + this.ordinalExtractor = OrdinalExtractor.getInstance(); + this.numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + + this.moreOrEqual = Pattern.compile(EnglishNumeric.MoreOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqual = Pattern.compile(EnglishNumeric.LessOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSuffix = Pattern.compile(EnglishNumeric.MoreOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSuffix = Pattern.compile(EnglishNumeric.LessOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSeparate = RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeMoreSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSeparate = RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeLessSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java new file mode 100644 index 000000000..80a2993d2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java @@ -0,0 +1,155 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public abstract class BaseNumberExtractor implements IExtractor { + + protected abstract Map getRegexes(); + + protected Map getAmbiguityFiltersDict() { + return null; + } + + protected abstract String getExtractType(); + + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + public List extract(String source) { + + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + + ArrayList result = new ArrayList<>(); + + Boolean[] matched = new Boolean[source.length()]; + Arrays.fill(matched, false); + + HashMap matchSource = new HashMap<>(); + + getRegexes().forEach((k, value) -> { + + Match[] matches = RegExpUtility.getMatches(k, source); + + for (Match m : matches) { + int start = m.index; + int length = m.length; + for (int j = 0; j < length; j++) { + matched[start + j] = true; + } + + // Keep Source Data for extra information + matchSource.put(m, value); + } + }); + + int last = -1; + for (int i = 0; i < source.length(); i++) { + + if (matched[i]) { + + if (i + 1 == source.length() || !matched[i + 1]) { + + int start = last + 1; + int length = i - last; + String subStr = source.substring(start, start + length); + + int finalStart = start; + int finalLength = length; + + Optional srcMatches = matchSource.keySet().stream().filter(o -> o.index == finalStart && o.length == finalLength).findFirst(); + + if (srcMatches.isPresent()) { + Match srcMatch = srcMatches.get(); + + // Extract negative numbers + if (getNegativeNumberTermsRegex().isPresent()) { + + Matcher match = getNegativeNumberTermsRegex().get().matcher(source.substring(0, start)); + if (match.find()) { + start = match.start(); + length = length + (match.end() - match.start()); + subStr = match.group() + subStr; + } + } + + ExtractResult er = new ExtractResult( + start, + length, + subStr, + getExtractType(), + matchSource.containsKey(srcMatch) ? matchSource.get(srcMatch) : null); + + result.add(er); + } + } + } else { + last = i; + } + } + + result = filterAmbiguity(result, source); + + return result; + } + + private ArrayList filterAmbiguity(ArrayList extractResults, String input) { + if (getAmbiguityFiltersDict() != null) { + for (Map.Entry pair : getAmbiguityFiltersDict().entrySet()) { + final Pattern key = pair.getKey(); + final Pattern value = pair.getValue(); + + for (ExtractResult extractResult : extractResults) { + Optional keyMatch = Arrays.stream(RegExpUtility.getMatches(key, extractResult.getText())).findFirst(); + if (keyMatch.isPresent()) { + final Match[] matches = RegExpUtility.getMatches(value, input); + extractResults = extractResults.stream() + .filter(er -> Arrays.stream(matches).noneMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) + .collect(Collectors.toCollection(ArrayList::new)); + } + } + } + } + + return extractResults; + } + + protected Pattern generateLongFormatNumberRegexes(LongFormatType type) { + return generateLongFormatNumberRegexes(type, BaseNumbers.PlaceHolderDefault); + } + + protected Pattern generateLongFormatNumberRegexes(LongFormatType type, String placeholder) { + + String thousandsMark = Pattern.quote(String.valueOf(type.thousandsMark)); + String decimalsMark = Pattern.quote(String.valueOf(type.decimalsMark)); + + String regexDefinition = type.decimalsMark == '\0' ? + BaseNumbers.IntegerRegexDefinition(placeholder, thousandsMark) : + BaseNumbers.DoubleRegexDefinition(placeholder, thousandsMark, decimalsMark); + + return RegExpUtility.getSafeLookbehindRegExp(regexDefinition, Pattern.UNICODE_CHARACTER_CLASS); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java new file mode 100644 index 000000000..c6540c350 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java @@ -0,0 +1,232 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberRangeConstants; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.javatuples.Pair; +import org.javatuples.Triplet; + +public abstract class BaseNumberRangeExtractor implements IExtractor { + + private final BaseNumberExtractor numberExtractor; + + private final BaseNumberExtractor ordinalExtractor; + + private final BaseNumberParser numberParser; + + protected abstract Map getRegexes(); + + protected String getExtractType() { + return ""; + } + + protected BaseNumberRangeExtractor(BaseNumberExtractor numberExtractor, BaseNumberExtractor ordinalExtractor, BaseNumberParser numberParser) { + this.numberExtractor = numberExtractor; + this.ordinalExtractor = ordinalExtractor; + this.numberParser = numberParser; + } + + @Override + public List extract(String source) { + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + Map, String> matchSource = new HashMap<>(); + boolean[] matched = new boolean[source.length()]; + Arrays.fill(matched, false); + + List> matches = new ArrayList<>(); + getRegexes().forEach((k, value) -> { + Matcher matcher = k.matcher(source); + if (matcher.find()) { + matcher.reset(); + matches.add(Pair.with(matcher, value)); + } + }); + + for (Pair pair : matches) { + Matcher matcher = pair.getValue0(); + String value = pair.getValue1(); + while (matcher.find()) { + int start = NumberRangeConstants.INVALID_NUM; + int length = NumberRangeConstants.INVALID_NUM; + Pair startAndLength = getMatchedStartAndLength(matcher, value, source, start, length); + start = startAndLength.getValue0(); + length = startAndLength.getValue1(); + + if (start >= 0 && length > 0) { + for (int j = 0; j < length; j++) { + matched[start + j] = true; + } + + // Keep Source Data for extra information + matchSource.put(Pair.with(start, length), value); + } + } + } + + int last = -1; + for (int i = 0; i < source.length(); i++) { + if (matched[i]) { + if (i + 1 == source.length() || !matched[i + 1]) { + int start = last + 1; + int length = i - last; + String substr = source.substring(start, start + length); + + Optional> srcMatches = matchSource.keySet().stream().filter(o -> o.getValue0() == start && o.getValue1() == length).findFirst(); + if (srcMatches.isPresent()) { + Pair srcMatch = srcMatches.get(); + ExtractResult er = new ExtractResult(start, length, substr, getExtractType(), matchSource.containsKey(srcMatch) ? matchSource.get(srcMatch) : null); + result.add(er); + } + } + } else { + last = i; + } + } + + return result; + } + + private Pair getMatchedStartAndLength(Matcher match, String type, String source, int start, int length) { + + Map groupValues = RegExpUtility.getNamedGroups(match, true); + String numberStr1 = groupValues.containsKey("number1") ? groupValues.get("number1") : ""; + String numberStr2 = groupValues.containsKey("number2") ? groupValues.get("number2") : ""; + + if (type.contains(NumberRangeConstants.TWONUM)) { + List extractNumList1 = extractNumberAndOrdinalFromStr(numberStr1); + List extractNumList2 = extractNumberAndOrdinalFromStr(numberStr2); + + if (extractNumList1 != null && extractNumList2 != null) { + if (type.contains(NumberRangeConstants.TWONUMTILL)) { + // num1 must have same type with num2 + if (!extractNumList1.get(0).getType().equals(extractNumList2.get(0).getType())) { + return Pair.with(start, length); + } + + // num1 must less than num2 + ParseResult numExt1 = numberParser.parse(extractNumList1.get(0)); + ParseResult numExt2 = numberParser.parse(extractNumList2.get(0)); + double num1 = numExt1.getValue() != null ? (double)numExt1.getValue() : 0; + double num2 = numExt1.getValue() != null ? (double)numExt2.getValue() : 0; + + if (num1 > num2) { + return Pair.with(start, length); + } + + extractNumList1.subList(1, extractNumList1.size()).clear(); + extractNumList2.subList(1, extractNumList2.size()).clear(); + } + + start = match.start(); + length = match.end() - start; + + Triplet num1 = validateMatchAndGetStartAndLength(extractNumList1, numberStr1, match, source, start, length); + start = num1.getValue1(); + length = num1.getValue2(); + Triplet num2 = validateMatchAndGetStartAndLength(extractNumList2, numberStr2, match, source, start, length); + start = num2.getValue1(); + length = num2.getValue2(); + + if (!num1.getValue0() || !num2.getValue0()) { + start = NumberRangeConstants.INVALID_NUM; + length = NumberRangeConstants.INVALID_NUM; + } + } + } else { + String numberStr = numberStr1 == null || numberStr1.isEmpty() ? numberStr2 : numberStr1; + + List extractNumList = extractNumberAndOrdinalFromStr(numberStr); + + if (extractNumList != null) { + start = match.start(); + length = match.end() - start; + + Triplet num = validateMatchAndGetStartAndLength(extractNumList, numberStr, match, source, start, length); + start = num.getValue1(); + length = num.getValue2(); + if (!num.getValue0()) { + start = NumberRangeConstants.INVALID_NUM; + length = NumberRangeConstants.INVALID_NUM; + } + } + } + + return Pair.with(start, length); + } + + private Triplet + validateMatchAndGetStartAndLength(List extractNumList, String numberStr, MatchResult match, String source, int start, int length) { + + boolean validNum = false; + + for (ExtractResult extractNum : extractNumList) { + if (numberStr.trim().endsWith(extractNum.getText()) && match.group().startsWith(numberStr)) { + start = source.indexOf(numberStr) + (extractNum.getStart() != null ? extractNum.getStart() : 0); + length = length - (extractNum.getStart() != null ? extractNum.getStart() : 0); + validNum = true; + } else if (extractNum.getStart() == 0 && match.group().endsWith(numberStr)) { + length = length - numberStr.length() + (extractNum.getLength() != null ? extractNum.getLength() : 0); + validNum = true; + } else if (extractNum.getStart() == 0 && extractNum.getLength() == numberStr.trim().length()) { + validNum = true; + } + + if (validNum) { + break; + } + } + + return Triplet.with(validNum, start, length); + } + + private List extractNumberAndOrdinalFromStr(String numberStr) { + List extractNumber = numberExtractor.extract(numberStr); + List extractOrdinal = ordinalExtractor.extract(numberStr); + + if (extractNumber.size() == 0) { + return extractOrdinal.size() == 0 ? null : extractOrdinal; + } + + if (extractOrdinal.size() == 0) { + return extractNumber; + } + + extractNumber.addAll(extractOrdinal); + + // extractNumber = extractNumber.OrderByDescending(num => num.Length).ThenByDescending(num => num.Start).ToList(); + Collections.sort(extractNumber, (Comparator)(o1, o2) -> { + Integer x1 = ((ExtractResult)o1).getLength(); + Integer x2 = ((ExtractResult)o2).getLength(); + int scomp = x2.compareTo(x1); + + if (scomp != 0) { + return scomp; + } + + x1 = ((ExtractResult)o1).getStart(); + x2 = ((ExtractResult)o2).getStart(); + return x2.compareTo(x1); + }); + + return extractNumber; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java new file mode 100644 index 000000000..a7c5fe57b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java @@ -0,0 +1,241 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberOptions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public abstract class BasePercentageExtractor implements IExtractor { + + private final BaseNumberExtractor numberExtractor; + + protected abstract Set getRegexes(); + + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + protected String getExtractType() { + return Constants.SYS_NUM_PERCENTAGE; + } + + protected static final String NumExtType = Constants.SYS_NUM; + + protected static final String FracNumExtType = Constants.SYS_NUM_FRACTION; + + protected BasePercentageExtractor(BaseNumberExtractor numberExtractor) { + this.numberExtractor = numberExtractor; + } + + public List extract(String source) { + + String originSource = source; + // preprocess the source sentence via extracting and replacing the numbers in it + PreProcessResult preProcessResult = preProcessStrWithNumberExtracted(originSource); + source = preProcessResult.string; + Map positionMap = preProcessResult.positionMap; + List numExtResults = preProcessResult.numExtractResults; + + List allMatches = new ArrayList<>(); + // match percentage with regexes + for (Pattern regex : getRegexes()) { + allMatches.add(regex.matcher(source)); + } + + boolean[] matched = new boolean[source.length()]; + Arrays.fill(matched, false); + + for (Matcher matcher : allMatches) { + while (matcher.find()) { + MatchResult r = matcher.toMatchResult(); + int start = r.start(); + int end = r.end(); + int length = end - start; + for (int j = 0; j < length; j++) { + matched[j + start] = true; + } + } + } + + List result = new ArrayList<>(); + int last = -1; + + // get index of each matched results + for (int i = 0; i < source.length(); i++) { + if (matched[i]) { + if (i + 1 == source.length() || !matched[i + 1]) { + int start = last + 1; + int length = i - last; + String substr = source.substring(start, start + length); + ExtractResult er = new ExtractResult(start, length, substr, getExtractType(), null); + result.add(er); + } + } else { + last = i; + } + } + + // post-processing, restoring the extracted numbers + postProcessing(result, originSource, positionMap, numExtResults); + + return result; + } + + private void postProcessing(List results, String originSource, Map positionMap, List numExtResults) { + String replaceNumText = "@" + NumExtType; + String replaceFracNumText = "@" + FracNumExtType; + + for (int i = 0; i < results.size(); i++) { + int start = results.get(i).getStart(); + int end = start + results.get(i).getLength(); + String str = results.get(i).getText(); + List> data = new ArrayList<>(); + + String replaceText; + if ((getOptions().ordinal() & NumberOptions.PercentageMode.ordinal()) != 0 && str.contains(replaceFracNumText)) { + replaceText = replaceFracNumText; + } else { + replaceText = replaceNumText; + } + + if (positionMap.containsKey(start) && positionMap.containsKey(end)) { + int originStart = positionMap.get(start); + int originLength = positionMap.get(end) - originStart; + results.set(i, new ExtractResult(originStart, originLength, originSource.substring(originStart, originLength + originStart), results.get(i).getType(), null)); + + int numStart = str.indexOf(replaceText); + if (numStart != -1) { + if (positionMap.containsKey(numStart)) { + for (int j = i; j < numExtResults.size(); j++) { + ExtractResult r = results.get(i); + ExtractResult n = numExtResults.get(j); + if ((r.getStart().equals(n.getStart()) || + r.getStart() + r.getLength() == n.getStart() + n.getLength()) && + r.getText().contains(n.getText())) { + data.add(Pair.with(n.getText(), n)); + } + } + } + } + } + + if ((getOptions().ordinal() & NumberOptions.PercentageMode.ordinal()) != 0) { + // deal with special cases like " of" and "one in two" in percentageMode + if (str.contains(replaceFracNumText) || data.size() > 1) { + ExtractResult r = results.get(i); + results.set(i, new ExtractResult(r.getStart(), r.getLength(), r.getText(), r.getType(), data)); + } else if (data.size() == 1) { + ExtractResult r = results.get(i); + results.set(i, new ExtractResult(r.getStart(), r.getLength(), r.getText(), r.getType(), data.get(0))); + } + } else if (data.size() == 1) { + ExtractResult r = results.get(i); + results.set(i, new ExtractResult(r.getStart(), r.getLength(), r.getText(), r.getType(), data.get(0))); + } + } + } + + private PreProcessResult preProcessStrWithNumberExtracted(String input) { + + Map positionMap = new HashMap<>(); + List numExtractResults = numberExtractor.extract(input); + + String replaceNumText = "@" + NumExtType; + String replaceFracText = "@" + FracNumExtType; + boolean percentModeEnabled = (getOptions().ordinal() & NumberOptions.PercentageMode.ordinal()) != 0; + + //@TODO potential cause of GC + int[] match = new int[input.length()]; + Arrays.fill(match, 0); + List> strParts = new ArrayList<>(); + int start; + int end; + + for (int i = 0; i < numExtractResults.size(); i++) { + ExtractResult extraction = numExtractResults.get(i); + start = extraction.getStart(); + end = extraction.getLength() + start; + for (int j = start; j < end; j++) { + if (match[j] == 0) { + if (percentModeEnabled && extraction.getData().toString().startsWith("Frac")) { + match[j] = -(i + 1); + } else { + match[j] = i + 1; + } + } + } + } + + start = 0; + for (int i = 1; i < input.length(); i++) { + if (match[i] != match[i - 1]) { + strParts.add(Pair.with(start, i - 1)); + start = i; + } + } + + strParts.add(Pair.with(start, input.length() - 1)); + + String ret = ""; + int index = 0; + for (Pair strPart : strParts) { + start = strPart.getValue0(); + end = strPart.getValue1(); + int type = match[start]; + + if (type == 0) { + // subsequence which won't be extracted + ret += input.substring(start, end + 1); + for (int i = start; i <= end; i++) { + positionMap.put(index++, i); + } + } else { + // subsequence which will be extracted as number, type is negative for fraction number extraction + String replaceText = type > 0 ? replaceNumText : replaceFracText; + ret += replaceText; + for (int i = 0; i < replaceText.length(); i++) { + positionMap.put(index++, start); + } + } + } + + positionMap.put(index, input.length()); + + return new PreProcessResult(ret, positionMap, numExtractResults); + } + + protected static Set buildRegexes(Set regexStrs) { + return buildRegexes(regexStrs, true); + } + + protected static Set buildRegexes(Set regexStrs, boolean ignoreCase) { + + Set regexes = new HashSet<>(); + for (String regexStr : regexStrs) { + //var sl = "(?=\\b)(" + regexStr + ")(?=(s?\\b))"; + + int options = 0; + if (ignoreCase) { + options = options | Pattern.CASE_INSENSITIVE; + } + + Pattern regex = Pattern.compile(regexStr, options); + + regexes.add(regex); + } + + return Collections.unmodifiableSet(regexes); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java new file mode 100644 index 000000000..b2f439b04 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.util.List; +import java.util.Map; + +public class PreProcessResult { + public final String string; + public final Map positionMap; + public final List numExtractResults; + + public PreProcessResult(String string, Map positionMap, List numExtractResults) { + this.string = string; + this.positionMap = positionMap; + this.numExtractResults = numExtractResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java new file mode 100644 index 000000000..974c6bb73 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java @@ -0,0 +1,55 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(FrenchNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + // Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + // Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java new file mode 100644 index 000000000..6d0ea2f65 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(FrenchNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "Double" + FrenchNumeric.LangMarker); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java new file mode 100644 index 000000000..18f855c70 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + FrenchNumeric.LangMarker); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + FrenchNumeric.LangMarker); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + FrenchNumeric.LangMarker); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java new file mode 100644 index 000000000..f349f0882 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(FrenchNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumDot, placeholder), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + FrenchNumeric.LangMarker); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + FrenchNumeric.LangMarker); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java new file mode 100644 index 000000000..4ce68f8f3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java @@ -0,0 +1,106 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + private final Map regexes; + private final Map ambiguityFiltersDict; + private final NumberOptions options; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private NumberExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + HashMap builder = new HashMap<>(); + + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(FrenchNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : FrenchNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..188357df1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java @@ -0,0 +1,35 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.OrdinalFrenchRegex, Pattern.UNICODE_CHARACTER_CLASS), "Ord" + FrenchNumeric.LangMarker); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java new file mode 100644 index 000000000..476ade10b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(FrenchNumeric.NumberWithSuffixPercentage); + builder.add(FrenchNumeric.NumberWithPrefixPercentage); + + this.regexes = buildRegexes(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java new file mode 100644 index 000000000..e878ca632 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java @@ -0,0 +1,105 @@ +package com.microsoft.recognizers.text.number.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class FrenchNumberParserConfiguration extends BaseNumberParserConfiguration { + + public FrenchNumberParserConfiguration() { + this(NumberOptions.None); + } + + public FrenchNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.French), options); + } + + public FrenchNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + FrenchNumeric.LangMarker, + cultureInfo, + FrenchNumeric.CompoundNumberLanguage, + FrenchNumeric.MultiDecimalSeparatorCulture, + options, + FrenchNumeric.NonDecimalSeparatorChar, + FrenchNumeric.DecimalSeparatorChar, + FrenchNumeric.FractionMarkerToken, + FrenchNumeric.HalfADozenText, + FrenchNumeric.WordSeparatorToken, + FrenchNumeric.WrittenDecimalSeparatorTexts, + FrenchNumeric.WrittenGroupSeparatorTexts, + FrenchNumeric.WrittenIntegerSeparatorTexts, + FrenchNumeric.WrittenFractionSeparatorTexts, + FrenchNumeric.CardinalNumberMap, + buildOrdinalNumberMap(), + FrenchNumeric.RoundNumberMap, + + Pattern.compile(FrenchNumeric.HalfADozenRegex, Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(FrenchNumeric.DigitalNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(FrenchNumeric.NegativeNumberSignRegex, Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(FrenchNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS)); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + return new ArrayList(tokens); + } + + @Override + public long resolveCompositeNumber(String numberStr) { + + Map ordinalNumberMap = getOrdinalNumberMap(); + Map cardinalNumberMap = getCardinalNumberMap(); + + if (ordinalNumberMap.containsKey(numberStr)) { + return ordinalNumberMap.get(numberStr); + } + + if (cardinalNumberMap.containsKey(numberStr)) { + return cardinalNumberMap.get(numberStr); + } + + long value = 0; + long finalValue = 0; + StringBuilder strBuilder = new StringBuilder(); + int lastGoodChar = 0; + for (int i = 0; i < numberStr.length(); i++) { + strBuilder.append(numberStr.charAt(i)); + + String tmp = strBuilder.toString(); + if (cardinalNumberMap.containsKey(tmp) && cardinalNumberMap.get(tmp) > value) { + lastGoodChar = i; + value = cardinalNumberMap.get(tmp); + } + + if ((i + 1) == numberStr.length()) { + finalValue += value; + strBuilder = new StringBuilder(); + i = lastGoodChar++; + value = 0; + } + } + + return finalValue; + } + + private static Map buildOrdinalNumberMap() { + ImmutableMap.Builder builder = new ImmutableMap.Builder() + .putAll(FrenchNumeric.OrdinalNumberMap); + + FrenchNumeric.SuffixOrdinalMap.forEach((suffixKey, suffixValue) -> + FrenchNumeric.PrefixCardinalMap.forEach((prefixKey, prefixValue) -> + builder.put(prefixKey + suffixKey, prefixValue * suffixValue))); + + return builder.build(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java new file mode 100644 index 000000000..43ad6dbd5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java @@ -0,0 +1,55 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(GermanNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + // Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + // Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java new file mode 100644 index 000000000..acff62fa1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(GermanNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS),"DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS),"DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java new file mode 100644 index 000000000..c6578fd41 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracGer"); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracGer"); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java new file mode 100644 index 000000000..2cc1b8c21 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java @@ -0,0 +1,49 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(GermanNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.NumbersWithPlaceHolder(placeholder), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.RoundNumberIntegerRegexWithLocks, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.NumbersWithDozenSuffix,Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.AllIntRegexWithLocks, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerGer"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumComma, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java new file mode 100644 index 000000000..7339e7264 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java @@ -0,0 +1,110 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + private final NumberOptions options; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + + private NumberExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(GermanNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex), "IntegerNum"); + break; + case Default: + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + // Add Fraction + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : GermanNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..8a7ae08e5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalNumericRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalGermanRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalRoundNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdGer"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java new file mode 100644 index 000000000..643e834c7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(GermanNumeric.NumberWithSuffixPercentage); + builder.add(GermanNumeric.NumberWithPrefixPercentage); + this.regexes = buildRegexes(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java new file mode 100644 index 000000000..a301ebbd1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java @@ -0,0 +1,113 @@ +package com.microsoft.recognizers.text.number.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class GermanNumberParserConfiguration extends BaseNumberParserConfiguration { + + public GermanNumberParserConfiguration() { + this(NumberOptions.None); + } + + public GermanNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.German), options); + } + + public GermanNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + GermanNumeric.LangMarker, + cultureInfo, + GermanNumeric.CompoundNumberLanguage, + GermanNumeric.MultiDecimalSeparatorCulture, + options, + GermanNumeric.NonDecimalSeparatorChar, + GermanNumeric.DecimalSeparatorChar, + GermanNumeric.FractionMarkerToken, + GermanNumeric.HalfADozenText, + GermanNumeric.WordSeparatorToken, + GermanNumeric.WrittenDecimalSeparatorTexts, + GermanNumeric.WrittenGroupSeparatorTexts, + GermanNumeric.WrittenIntegerSeparatorTexts, + GermanNumeric.WrittenFractionSeparatorTexts, + GermanNumeric.CardinalNumberMap, + GermanNumeric.OrdinalNumberMap, + GermanNumeric.RoundNumberMap, + Pattern.compile(GermanNumeric.HalfADozenRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(GermanNumeric.DigitalNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(GermanNumeric.NegativeNumberSignRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(GermanNumeric.FractionPrepositionRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS)); + } + + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + List fracWords = new ArrayList<>(); + List tokenList = new ArrayList<>(tokens); + int tokenLen = tokenList.size(); + + for (int i = 0; i < tokenLen; i++) { + if (tokenList.get(i).contains("-")) { + String[] splitedTokens = tokenList.get(i).split(Pattern.quote("-")); + if (splitedTokens.length == 2 && getOrdinalNumberMap().containsKey(splitedTokens[1])) { + fracWords.add(splitedTokens[0]); + fracWords.add(splitedTokens[1]); + } else { + fracWords.add(tokenList.get(i)); + } + } else if (i < tokenLen - 2 && tokenList.get(i + 1).equals("-")) { + if (getOrdinalNumberMap().containsKey(tokenList.get(i + 2))) { + fracWords.add(tokenList.get(i)); + fracWords.add(tokenList.get(i + 2)); + } else { + fracWords.add(tokenList.get(i) + tokenList.get(i + 1) + tokenList.get(i + 2)); + } + + i += 2; + } else { + fracWords.add(tokenList.get(i)); + } + } + + return fracWords; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + + Map ordinalNumberMap = getOrdinalNumberMap(); + Map cardinalNumberMap = getCardinalNumberMap(); + + if (numberStr.contains("-")) { + String[] numbers = numberStr.split(Pattern.quote("-")); + long ret = 0; + for (String number : numbers) { + if (ordinalNumberMap.containsKey(number)) { + ret += ordinalNumberMap.get(number); + } else if (cardinalNumberMap.containsKey(number)) { + ret += cardinalNumberMap.get(number); + } + } + + return ret; + } + + if (ordinalNumberMap.containsKey(numberStr)) { + return ordinalNumberMap.get(numberStr); + } + + if (cardinalNumberMap.containsKey(numberStr)) { + return cardinalNumberMap.get(numberStr); + } + + return 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java new file mode 100644 index 000000000..95101a349 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java @@ -0,0 +1,63 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.ResolutionKey; +import com.microsoft.recognizers.text.utilities.QueryProcessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public abstract class AbstractNumberModel implements IModel { + + protected final IParser parser; + protected final IExtractor extractor; + + protected AbstractNumberModel(IParser parser, IExtractor extractor) { + this.parser = parser; + this.extractor = extractor; + } + + @Override + public List parse(String query) { + + // Pre-process the query + query = QueryProcessor.preprocess(query, true); + + List parsedNumbers = new ArrayList(); + + try { + List extractResults = extractor.extract(query); + for (ExtractResult result : extractResults) { + ParseResult parsedResult = parser.parse(result); + if (parsedResult != null) { + parsedNumbers.add(parsedResult); + } + } + } catch (Exception ex) { + // Nothing to do. Exceptions in parse should not break users of recognizers. + // No result. + ex.printStackTrace(); + } + + return parsedNumbers.stream().map(o -> { + SortedMap sortedMap = new TreeMap(); + sortedMap.put(ResolutionKey.Value, o.getResolutionStr()); + + return new ModelResult( + o.getText(), + o.getStart(), + o.getStart() + o.getLength(), + getModelTypeName(), + sortedMap + ); + }).collect(Collectors.toCollection(ArrayList::new)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java new file mode 100644 index 000000000..cf064a3af --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class NumberModel extends AbstractNumberModel { + + public NumberModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_NUMBER; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java new file mode 100644 index 000000000..d0da81cf6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class NumberRangeModel extends AbstractNumberModel { + + public NumberRangeModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_NUMBERRANGE; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java new file mode 100644 index 000000000..f5390a84c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class OrdinalModel extends AbstractNumberModel { + + public OrdinalModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_ORDINAL; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java new file mode 100644 index 000000000..71adba257 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class PercentModel extends AbstractNumberModel { + + public PercentModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_PERCENTAGE; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java new file mode 100644 index 000000000..9e46d8290 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java @@ -0,0 +1,52 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.number.Constants; + +import java.util.Arrays; + +public abstract class AgnosticNumberParserFactory { + + public static BaseNumberParser getParser(AgnosticNumberParserType type, INumberParserConfiguration languageConfiguration) { + + boolean isChinese = languageConfiguration.getCultureInfo().cultureCode.equalsIgnoreCase(Culture.Chinese); + boolean isJapanese = languageConfiguration.getCultureInfo().cultureCode.equalsIgnoreCase(Culture.Japanese); + + BaseNumberParser parser; + + + if (isChinese || isJapanese) { + parser = new BaseCJKNumberParser(languageConfiguration); + } else { + parser = new BaseNumberParser(languageConfiguration); + } + + switch (type) { + case Cardinal: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_CARDINAL, Constants.SYS_NUM_INTEGER, Constants.SYS_NUM_DOUBLE)); + break; + case Double: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_DOUBLE)); + break; + case Fraction: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_FRACTION)); + break; + case Integer: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_INTEGER)); + break; + case Ordinal: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_ORDINAL)); + break; + case Percentage: + if (!isChinese && !isJapanese) { + parser = new BasePercentageParser(languageConfiguration); + } + break; + default: + break; + } + + return parser; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java new file mode 100644 index 000000000..a505ff5a5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.number.parsers; + +public enum AgnosticNumberParserType { + Cardinal, + Double, + Fraction, + Integer, + Number, + Ordinal, + Percentage +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java new file mode 100644 index 000000000..cf6e6167a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java @@ -0,0 +1,498 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +public class BaseCJKNumberParser extends BaseNumberParser { + + protected final ICJKNumberParserConfiguration cjkConfig; + + public BaseCJKNumberParser(INumberParserConfiguration config) { + super(config); + this.cjkConfig = (ICJKNumberParserConfiguration)config; + } + + @Override + public ParseResult parse(ExtractResult extResult) { + + // check if the parser is configured to support specific types + if (supportedTypes.isPresent() && !supportedTypes.get().stream().anyMatch(t -> extResult.getType().equals(t))) { + return null; + } + + String extra = extResult.getData() instanceof String ? (String)extResult.getData() : null; + ParseResult ret = null; + + ExtractResult getExtResult = new ExtractResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), extResult.getData()); + + if (config.getCultureInfo().cultureCode.equalsIgnoreCase("zh-CN")) { + getExtResult.setText(replaceTraWithSim(getExtResult.getText())); + } + + if (extra == null) { + return null; + } + + if (extra.contains("Per")) { + ret = parsePercentage(getExtResult); + } else if (extra.contains("Num")) { + getExtResult.setText(normalizeCharWidth(getExtResult.getText())); + ret = digitNumberParse(getExtResult); + if (config.getNegativeNumberSignRegex().matcher(getExtResult.getText()).find() && (double)ret.getValue() > 0) { + ret.setValue(-(double)ret.getValue()); + } + + ret.setResolutionStr(getResolutionString((double)ret.getValue())); + } else if (extra.contains("Pow")) { + getExtResult.setText(normalizeCharWidth(getExtResult.getText())); + ret = powerNumberParse(getExtResult); + ret.setResolutionStr(getResolutionString((double)ret.getValue())); + } else if (extra.contains("Frac")) { + ret = parseFraction(getExtResult); + } else if (extra.contains("Dou")) { + ret = parseDouble(getExtResult); + } else if (extra.contains("Integer")) { + ret = parseInteger(getExtResult); + } else if (extra.contains("Ordinal")) { + ret = parseOrdinal(getExtResult); + } + + if (ret != null) { + ret.setText(extResult.getText().toLowerCase(Locale.ROOT)); + } + + return ret; + } + + // Parse Fraction phrase. + protected ParseResult parseFraction(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + + String resultText = extResult.getText(); + String[] splitResult = cjkConfig.getFracSplitRegex().split(resultText); + String intPart = ""; + String demoPart = ""; + String numPart = ""; + + if (splitResult.length == 3) { + intPart = splitResult[0]; + demoPart = splitResult[1]; + numPart = splitResult[2]; + } else { + intPart = String.valueOf(cjkConfig.getZeroChar()); + demoPart = splitResult[0]; + numPart = splitResult[1]; + } + + Pattern digitNumRegex = cjkConfig.getDigitNumRegex(); + Pattern pointRegex = cjkConfig.getPointRegex(); + + double intValue = digitNumRegex.matcher(intPart).find() ? + getDigitValue(intPart, 1.0) : + getIntValue(intPart); + + double numValue = digitNumRegex.matcher(numPart).find() ? + getDigitValue(numPart, 1.0) : + (pointRegex.matcher(numPart).find() ? + getIntValue(pointRegex.split(numPart)[0]) + getPointValue(pointRegex.split(numPart)[1]) : + getIntValue(numPart)); + + double demoValue = digitNumRegex.matcher(demoPart).find() ? + getDigitValue(demoPart, 1.0) : + getIntValue(demoPart); + + + if (cjkConfig.getNegativeNumberSignRegex().matcher(intPart).find()) { + result.setValue(intValue - numValue / demoValue); + } else { + result.setValue(intValue + numValue / demoValue); + } + + result.setResolutionStr(getResolutionString((double)result.getValue())); + return result; + } + + // Parse percentage phrase. + protected ParseResult parsePercentage(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + Map zeroToNineMap = cjkConfig.getZeroToNineMap(); + + String resultText = extResult.getText(); + long power = 1; + + if (extResult.getData().toString().contains("Spe")) { + resultText = normalizeCharWidth(resultText); + resultText = replaceUnit(resultText); + + if (resultText.equals("半額") || resultText.equals("半値") || resultText.equals("半折")) { + result.setValue(50); + } else if (resultText.equals("10成") || resultText.equals("10割") || resultText.equals("十割")) { + result.setValue(100); + } else { + Match[] matches = RegExpUtility.getMatches(this.cjkConfig.getSpeGetNumberRegex(), resultText); + double intNumber; + + if (matches.length == 2) { + char intNumberChar = matches[0].value.charAt(0); + + if (intNumberChar == cjkConfig.getPairChar()) { + intNumber = 5; + } else if (cjkConfig.getTenChars().contains(intNumberChar)) { + intNumber = 10; + } else { + intNumber = zeroToNineMap.get(intNumberChar); + } + + char pointNumberChar = matches[1].value.charAt(0); + double pointNumber; + if (pointNumberChar == '半') { + pointNumber = 0.5; + } else { + pointNumber = zeroToNineMap.get(pointNumberChar) * 0.1; + } + + result.setValue((intNumber + pointNumber) * 10); + } else if (matches.length == 5) { + // Deal the Japanese percentage case like "xxx割xxx分xxx厘", get the integer value and convert into result. + char intNumberChar = matches[0].value.charAt(0); + char pointNumberChar = matches[1].value.charAt(0); + char dotNumberChar = matches[3].value.charAt(0); + + double pointNumber = zeroToNineMap.get(pointNumberChar) * 0.1; + double dotNumber = zeroToNineMap.get(dotNumberChar) * 0.01; + + intNumber = zeroToNineMap.get(intNumberChar); + + result.setValue((intNumber + pointNumber + dotNumber) * 10); + } else { + char intNumberChar = matches[0].value.charAt(0); + + if (intNumberChar == cjkConfig.getPairChar()) { + intNumber = 5; + } else if (cjkConfig.getTenChars().contains(intNumberChar)) { + intNumber = 10; + } else { + intNumber = zeroToNineMap.get(intNumberChar); + } + + result.setValue(intNumber * 10); + } + } + } else if (extResult.getData().toString().contains("Num")) { + + Match[] doubleMatches = RegExpUtility.getMatches(cjkConfig.getPercentageRegex(), resultText); + String doubleText = doubleMatches[doubleMatches.length - 1].value; + + if (doubleText.contains("k") || doubleText.contains("K") || doubleText.contains("k") || + doubleText.contains("K")) { + power = 1000; + } + + if (doubleText.contains("M") || doubleText.contains("M")) { + power = 1000000; + } + + if (doubleText.contains("G") || doubleText.contains("G")) { + power = 1000000000; + } + + if (doubleText.contains("T") || doubleText.contains("T")) { + power = 1000000000000L; + } + + result.setValue(getDigitValue(resultText, power)); + } else { + Match[] doubleMatches = RegExpUtility.getMatches(cjkConfig.getPercentageRegex(), resultText); + String doubleText = doubleMatches[doubleMatches.length - 1].value; + + doubleText = replaceUnit(doubleText); + + String[] splitResult = cjkConfig.getPointRegex().split(doubleText); + if (splitResult[0].equals("")) { + splitResult[0] = String.valueOf(cjkConfig.getZeroChar()); + } + + double doubleValue = getIntValue(splitResult[0]); + if (splitResult.length == 2) { + if (cjkConfig.getNegativeNumberSignRegex().matcher(splitResult[0]).find()) { + doubleValue -= getPointValue(splitResult[1]); + } else { + doubleValue += getPointValue(splitResult[1]); + } + } + + result.setValue(doubleValue); + } + + Match[] matches = RegExpUtility.getMatches(this.cjkConfig.getPercentageNumRegex(), resultText); + if (matches.length > 0) { + String demoString = matches[0].value; + String[] splitResult = cjkConfig.getFracSplitRegex().split(demoString); + String demoPart = splitResult[0]; + + Pattern digitNumRegex = cjkConfig.getDigitNumRegex(); + + double demoValue = digitNumRegex.matcher(demoPart).find() ? + getDigitValue(demoPart, 1.0) : + getIntValue(demoPart); + if (demoValue < 100) { + result.setValue((double)result.getValue() * (100 / demoValue)); + } else { + result.setValue((double)result.getValue() / (demoValue / 100)); + } + } + + if (result.getValue() instanceof Double) { + result.setResolutionStr(getResolutionString((double)result.getValue()) + "%"); + } else if (result.getValue() instanceof Integer) { + result.setResolutionStr(getResolutionString((int)result.getValue()) + "%"); + } + + return result; + } + + // Parse ordinal phrase. + protected ParseResult parseOrdinal(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + + String resultText = extResult.getText(); + resultText = resultText.substring(1); + + boolean isDigit = cjkConfig.getDigitNumRegex().matcher(resultText).find(); + boolean isRoundInt = cjkConfig.getRoundNumberIntegerRegex().matcher(resultText).find(); + + double newValue = isDigit && !isRoundInt ? getDigitValue(resultText, 1) : getIntValue(resultText); + + result.setValue(newValue); + result.setResolutionStr(getResolutionString(newValue)); + + return result; + } + + // Parse double phrase + protected ParseResult parseDouble(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + String resultText = extResult.getText(); + + if (cjkConfig.getDoubleAndRoundRegex().matcher(resultText).find()) { + resultText = replaceUnit(resultText); + result.setValue(getDigitValue( + resultText.substring(0, resultText.length() - 1), + cjkConfig.getRoundNumberMapChar().get(resultText.charAt(resultText.length() - 1)))); + } else { + resultText = replaceUnit(resultText); + String[] splitResult = cjkConfig.getPointRegex().split(resultText); + + if (splitResult[0].equals("")) { + splitResult[0] = String.valueOf(cjkConfig.getZeroChar()); + } + + if (cjkConfig.getNegativeNumberSignRegex().matcher(splitResult[0]).find()) { + result.setValue(getIntValue(splitResult[0]) - getPointValue(splitResult[1])); + } else { + result.setValue(getIntValue(splitResult[0]) + getPointValue(splitResult[1])); + } + } + + result.setResolutionStr(getResolutionString((double)result.getValue())); + return result; + } + + // Parse integer phrase + protected ParseResult parseInteger(ExtractResult extResult) { + double value = getIntValue(extResult.getText()); + return new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), extResult.getText(), value, getResolutionString(value)); + } + + // Replace traditional Chinese characters with simpilified Chinese ones. + private String replaceTraWithSim(String text) { + if (StringUtility.isNullOrWhiteSpace(text)) { + return text; + } + + Map tratoSimMap = cjkConfig.getTratoSimMap(); + + StringBuilder builder = new StringBuilder(); + text.chars().mapToObj(i -> (char)i).forEach(c -> { + builder.append(tratoSimMap.containsKey(c) ? tratoSimMap.get(c) : c); + }); + + return builder.toString(); + } + + // Replace full digtal numbers with half digtal numbers. "4" and "4" are both legal in Japanese, replace "4" with "4", then deal with "4" + private String normalizeCharWidth(String text) { + if (StringUtility.isNullOrWhiteSpace(text)) { + return text; + } + + Map fullToHalfMap = cjkConfig.getFullToHalfMap(); + StringBuilder builder = new StringBuilder(); + text.chars().mapToObj(i -> (char)i).forEach(c -> { + builder.append(fullToHalfMap.containsKey(c) ? fullToHalfMap.get(c) : c); + }); + + return builder.toString(); + } + + // Parse unit phrase. "万", "億",... + private String replaceUnit(String resultText) { + for (Map.Entry p : cjkConfig.getUnitMap().entrySet()) { + resultText = resultText.replace(p.getKey(), p.getValue()); + } + + return resultText; + } + + private double getDigitValue(String intStr, double power) { + boolean isNegative = false; + + if (cjkConfig.getNegativeNumberSignRegex().matcher(intStr).find()) { + isNegative = true; + intStr = intStr.substring(1); + } + + intStr = normalizeCharWidth(intStr); + double intValue = getDigitalValue(intStr, power); + if (isNegative) { + intValue = -intValue; + } + + return intValue; + } + + private double getIntValue(String intStr) { + Map roundNumberMapChar = cjkConfig.getRoundNumberMapChar(); + + intStr = replaceUnit(intStr); + double intValue = 0; + double partValue = 0; + double beforeValue = 1; + + boolean isRoundBefore = false; + long roundBefore = -1; + long roundDefault = 1; + boolean isNegative = false; + boolean hasPreviousDigits = false; + + boolean isDozen = false; + boolean isPair = false; + + if (cjkConfig.getDozenRegex().matcher(intStr).find()) { + isDozen = true; + if (cjkConfig.getCultureInfo().cultureCode.equalsIgnoreCase("zh-CN")) { + intStr = intStr.substring(0, intStr.length() - 1); + } else if (cjkConfig.getCultureInfo().cultureCode.equalsIgnoreCase("ja-JP")) { + intStr = intStr.substring(0, intStr.length() - 3); + } + + } else if (cjkConfig.getPairRegex().matcher(intStr).find()) { + isPair = true; + intStr = intStr.substring(0, intStr.length() - 1); + } + + if (cjkConfig.getNegativeNumberSignRegex().matcher(intStr).find()) { + isNegative = true; + intStr = intStr.substring(1); + } + + for (int i = 0; i < intStr.length(); i++) { + if (roundNumberMapChar.containsKey(intStr.charAt(i))) { + + Long roundRecent = roundNumberMapChar.get(intStr.charAt(i)); + if (roundBefore != -1 && roundRecent > roundBefore) { + if (isRoundBefore) { + intValue += partValue * roundRecent; + isRoundBefore = false; + } else { + partValue += beforeValue * roundDefault; + intValue += partValue * roundRecent; + } + roundBefore = -1; + partValue = 0; + } else { + isRoundBefore = true; + partValue += beforeValue * roundRecent; + roundBefore = roundRecent; + + if (i == intStr.length() - 1 || cjkConfig.getRoundDirectList().contains(intStr.charAt(i))) { + intValue += partValue; + partValue = 0; + } + } + + roundDefault = roundRecent / 10; + } else if (cjkConfig.getZeroToNineMap().containsKey(intStr.charAt(i))) { + if (i != intStr.length() - 1) { + boolean isNotRoundNext = cjkConfig.getTenChars().contains(intStr.charAt(i + 1)) || !roundNumberMapChar.containsKey(intStr.charAt(i + 1)); + if (intStr.charAt(i) == cjkConfig.getZeroChar() && isNotRoundNext) { + beforeValue = 1; + roundDefault = 1; + } else { + double currentDigit = cjkConfig.getZeroToNineMap().get(intStr.charAt(i)); + if (hasPreviousDigits) { + beforeValue = beforeValue * 10 + currentDigit; + } else { + beforeValue = currentDigit; + } + isRoundBefore = false; + } + } else { + if (Character.isDigit(intStr.charAt(i))) { + roundDefault = 1; + } + double currentDigit = cjkConfig.getZeroToNineMap().get(intStr.charAt(i)); + if (hasPreviousDigits) { + beforeValue = beforeValue * 10 + currentDigit; + } else { + beforeValue = currentDigit; + } + partValue += beforeValue * roundDefault; + intValue += partValue; + partValue = 0; + } + } + hasPreviousDigits = Character.isDigit(intStr.charAt(i)); + } + + if (isNegative) { + intValue = -intValue; + } + + if (isDozen) { + intValue = intValue * 12; + } else if (isPair) { + intValue = intValue * 2; + } + + return intValue; + } + + private double getPointValue(String pointStr) { + double pointValue = 0; + double scale = 0.1; + + Map zeroToNineMap = cjkConfig.getZeroToNineMap(); + + for (int i : pointStr.chars().toArray()) { + char c = (char)i; + pointValue += scale * zeroToNineMap.get(c); + scale *= 0.1; + } + + return pointValue; + } + + private String getResolutionString(double value) { + return config.getCultureInfo() != null ? + NumberFormatUtility.format(value, config.getCultureInfo()) : + String.valueOf(value); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java new file mode 100644 index 000000000..6d46c13ab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java @@ -0,0 +1,314 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.number.NumberOptions; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class BaseCJKNumberParserConfiguration implements ICJKNumberParserConfiguration { + + private final String langMarker; + private final CultureInfo cultureInfo; + private final NumberOptions options; + + private final char nonDecimalSeparatorChar; + private final char decimalSeparatorChar; + private final char zeroChar; + private final char pairChar; + private final String fractionMarkerToken; + private final String halfADozenText; + private final String wordSeparatorToken; + + private final List writtenDecimalSeparatorTexts; + private final List writtenGroupSeparatorTexts; + private final List writtenIntegerSeparatorTexts; + private final List writtenFractionSeparatorTexts; + + private final Map cardinalNumberMap; + private final Map ordinalNumberMap; + private final Map roundNumberMap; + + private final Pattern halfADozenRegex; + private final Pattern digitalNumberRegex; + private final Pattern negativeNumberSignRegex; + private final Pattern fractionPrepositionRegex; + + //region ICJKNumberParserConfiguration + private final Map zeroToNineMap; + private final Map roundNumberMapChar; + private final Map fullToHalfMap; + private final Map unitMap; + private final Map tratoSimMap; + + private final List roundDirectList; + private final List tenChars; + + private final Pattern fracSplitRegex; + private final Pattern digitNumRegex; + private final Pattern speGetNumberRegex; + private final Pattern percentageRegex; + private final Pattern percentageNumRegex; + private final Pattern pointRegex; + private final Pattern doubleAndRoundRegex; + private final Pattern pairRegex; + private final Pattern dozenRegex; + private final Pattern roundNumberIntegerRegex; + + private final boolean isCompoundNumberLanguage; + private final boolean isMultiDecimalSeparatorCulture; + + protected BaseCJKNumberParserConfiguration(String langMarker, CultureInfo cultureInfo, boolean isCompoundNumberLanguage, boolean isMultiDecimalSeparatorCulture, + NumberOptions options, char nonDecimalSeparatorChar, char decimalSeparatorChar, + String fractionMarkerToken, String halfADozenText, String wordSeparatorToken, List writtenDecimalSeparatorTexts, List writtenGroupSeparatorTexts, + List writtenIntegerSeparatorTexts, List writtenFractionSeparatorTexts, Map cardinalNumberMap, Map ordinalNumberMap, + Map roundNumberMap, Pattern halfADozenRegex, Pattern digitalNumberRegex, Pattern negativeNumberSignRegex, Pattern fractionPrepositionRegex, Map zeroToNineMap, Map roundNumberMapChar, Map fullToHalfMap, Map unitMap, Map tratoSimMap, + List roundDirectList, Pattern fracSplitRegex, Pattern digitNumRegex, Pattern speGetNumberRegex, Pattern percentageRegex, Pattern pointRegex, + Pattern doubleAndRoundRegex, Pattern pairRegex, Pattern dozenRegex, Pattern roundNumberIntegerRegex,char zeroChar, List tenChars,char pairChar, + Pattern percentageNumRegex) { + + this.langMarker = langMarker; + this.cultureInfo = cultureInfo; + this.isCompoundNumberLanguage = isCompoundNumberLanguage; + this.isMultiDecimalSeparatorCulture = isMultiDecimalSeparatorCulture; + this.options = options; + this.nonDecimalSeparatorChar = nonDecimalSeparatorChar; + this.decimalSeparatorChar = decimalSeparatorChar; + this.zeroChar = zeroChar; + this.pairChar = pairChar; + this.fractionMarkerToken = fractionMarkerToken; + this.halfADozenText = halfADozenText; + this.wordSeparatorToken = wordSeparatorToken; + this.writtenDecimalSeparatorTexts = writtenDecimalSeparatorTexts; + this.writtenGroupSeparatorTexts = writtenGroupSeparatorTexts; + this.writtenIntegerSeparatorTexts = writtenIntegerSeparatorTexts; + this.writtenFractionSeparatorTexts = writtenFractionSeparatorTexts; + this.cardinalNumberMap = cardinalNumberMap; + this.ordinalNumberMap = ordinalNumberMap; + this.roundNumberMap = roundNumberMap; + this.halfADozenRegex = halfADozenRegex; + this.digitalNumberRegex = digitalNumberRegex; + this.negativeNumberSignRegex = negativeNumberSignRegex; + this.fractionPrepositionRegex = fractionPrepositionRegex; + this.zeroToNineMap = zeroToNineMap; + this.roundNumberMapChar = roundNumberMapChar; + this.fullToHalfMap = fullToHalfMap; + this.unitMap = unitMap; + this.tratoSimMap = tratoSimMap; + this.roundDirectList = roundDirectList; + this.tenChars = tenChars; + this.fracSplitRegex = fracSplitRegex; + this.digitNumRegex = digitNumRegex; + this.speGetNumberRegex = speGetNumberRegex; + this.percentageRegex = percentageRegex; + this.pointRegex = pointRegex; + this.doubleAndRoundRegex = doubleAndRoundRegex; + this.pairRegex = pairRegex; + this.dozenRegex = dozenRegex; + this.roundNumberIntegerRegex = roundNumberIntegerRegex; + this.percentageNumRegex = percentageNumRegex; + } + //endregion + + @Override + public Map getZeroToNineMap() { + return this.zeroToNineMap; + } + + @Override + public Map getRoundNumberMapChar() { + return this.roundNumberMapChar; + } + + @Override + public Map getFullToHalfMap() { + return this.fullToHalfMap; + } + + @Override + public Map getUnitMap() { + return this.unitMap; + } + + @Override + public Map getTratoSimMap() { + return this.tratoSimMap; + } + + @Override + public List getRoundDirectList() { + return this.roundDirectList; + } + + @Override + public List getTenChars() { + return this.tenChars; + } + + @Override + public Pattern getFracSplitRegex() { + return this.fracSplitRegex; + } + + @Override + public Pattern getDigitNumRegex() { + return this.digitNumRegex; + } + + @Override + public Pattern getSpeGetNumberRegex() { + return this.speGetNumberRegex; + } + + @Override + public Pattern getPercentageRegex() { + return this.percentageRegex; + } + + @Override + public Pattern getPercentageNumRegex() { + return this.percentageNumRegex; + } + + @Override + public Pattern getPointRegex() { + return this.pointRegex; + } + + @Override + public Pattern getDoubleAndRoundRegex() { + return this.doubleAndRoundRegex; + } + + @Override + public Pattern getPairRegex() { + return this.pairRegex; + } + + @Override + public Pattern getDozenRegex() { + return this.dozenRegex; + } + + @Override + public Pattern getRoundNumberIntegerRegex() { + return this.roundNumberIntegerRegex; + } + + @Override + public Map getCardinalNumberMap() { + return this.cardinalNumberMap; + } + + @Override + public Map getOrdinalNumberMap() { + return this.ordinalNumberMap; + } + + @Override + public Map getRoundNumberMap() { + return this.roundNumberMap; + } + + @Override + public NumberOptions getOptions() { + return this.options; + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public Pattern getDigitalNumberRegex() { + return this.digitalNumberRegex; + } + + @Override + public Pattern getFractionPrepositionRegex() { + return this.fractionPrepositionRegex; + } + + @Override + public String getFractionMarkerToken() { + return this.fractionMarkerToken; + } + + @Override + public Pattern getHalfADozenRegex() { + return this.halfADozenRegex; + } + + @Override + public String getHalfADozenText() { + return this.halfADozenText; + } + + @Override + public String getLangMarker() { + return this.langMarker; + } + + @Override + public char getNonDecimalSeparatorChar() { + return this.nonDecimalSeparatorChar; + } + + @Override + public char getDecimalSeparatorChar() { + return this.decimalSeparatorChar; + } + + @Override + public char getZeroChar() { + return this.zeroChar; + } + + @Override + public char getPairChar() { + return this.pairChar; + } + + @Override + public String getWordSeparatorToken() { + return this.wordSeparatorToken; + } + + @Override + public List getWrittenDecimalSeparatorTexts() { + return this.writtenDecimalSeparatorTexts; + } + + @Override + public List getWrittenGroupSeparatorTexts() { + return this.writtenGroupSeparatorTexts; + } + + @Override + public List getWrittenIntegerSeparatorTexts() { + return this.writtenIntegerSeparatorTexts; + } + + @Override + public List getWrittenFractionSeparatorTexts() { + return this.writtenFractionSeparatorTexts; + } + + @Override + public Pattern getNegativeNumberSignRegex() { + return this.negativeNumberSignRegex; + } + + @Override + public boolean isCompoundNumberLanguage() { + return this.isCompoundNumberLanguage; + } + + @Override + public boolean isMultiDecimalSeparatorCulture() { + return this.isMultiDecimalSeparatorCulture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java new file mode 100644 index 000000000..795aebeb7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java @@ -0,0 +1,635 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.utilities.QueryProcessor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BaseNumberParser implements IParser { + + protected final INumberParserConfiguration config; + + protected final Pattern textNumberRegex; + + protected final Pattern longFormatRegex; + + protected final Set roundNumberSet; + + protected Optional> supportedTypes = Optional.empty(); + + public void setSupportedTypes(List types) { + this.supportedTypes = Optional.of(types); + } + + public BaseNumberParser(INumberParserConfiguration config) { + this.config = config; + + String singleIntFrac = config.getWordSeparatorToken() + "| -|" + + getKeyRegex(config.getCardinalNumberMap().keySet()) + "|" + + getKeyRegex(config.getOrdinalNumberMap().keySet()); + + // Necessary for the german language because bigger numbers are not separated by whitespaces or special characters like in other languages + if (config.getCultureInfo().cultureCode.equalsIgnoreCase("de-DE")) { + this.textNumberRegex = Pattern.compile("(" + singleIntFrac + ")", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } else { + this.textNumberRegex = Pattern.compile("(?<=\\b)(" + singleIntFrac + ")(?=\\b)", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } + + this.longFormatRegex = Pattern.compile("\\d+", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.roundNumberSet = new HashSet<>(config.getRoundNumberMap().keySet()); + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + // check if the parser is configured to support specific types + if (supportedTypes.isPresent() && !this.supportedTypes.get().contains(extractResult.getType())) { + return null; + } + + String extra; + ParseResult ret = null; + + if (extractResult.getData() instanceof String) { + extra = (String)extractResult.getData(); + } else { + if (this.longFormatRegex.matcher(extractResult.getText()).find()) { + extra = "Num"; + } else { + extra = config.getLangMarker(); + } + } + + // Resolve symbol prefix + boolean isNegative = false; + Matcher matchNegative = config.getNegativeNumberSignRegex().matcher(extractResult.getText()); + if (matchNegative.find()) { + isNegative = true; + extractResult = new ExtractResult( + extractResult.getStart(), + extractResult.getLength(), + extractResult.getText().substring(matchNegative.group(1).length()), + extractResult.getType(), + extractResult.getData(), + extractResult.getMetadata()); + } + + if (extra.contains("Num")) { + ret = digitNumberParse(extractResult); + } else if (extra.contains("Frac" + config.getLangMarker())) { + // Frac is a special number, parse via another method + ret = fracLikeNumberParse(extractResult); + } else if (extra.contains(config.getLangMarker())) { + ret = textNumberParse(extractResult); + } else if (extra.contains("Pow")) { + ret = powerNumberParse(extractResult); + } + + if (ret != null && ret.getValue() != null) { + if (isNegative) { + // Recover to the original extracted Text + ret = new ParseResult( + ret.getStart(), + ret.getLength(), + matchNegative.group(1) + extractResult.getText(), + ret.getType(), + ret.getData(), + -(double)ret.getValue(), + ret.getResolutionStr()); + } + + String resolutionStr = config.getCultureInfo() != null ? NumberFormatUtility.format(ret.getValue(), config.getCultureInfo()) : ret.getValue().toString(); + ret.setResolutionStr(resolutionStr); + } + + if (ret != null) { + ret.setText(ret.getText().toLowerCase(Locale.ROOT)); + } + + return ret; + } + + /** + * Precondition: ExtResult must have arabic numerals. + * + * @param extractResult input arabic number + * @return + */ + protected ParseResult digitNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), + extractResult.getType(),null,null,null); + + //[1] 24 + //[2] 12 32/33 + //[3] 1,000,000 + //[4] 234.567 + //[5] 44/55 + //[6] 2 hundred + //dot occurred. + double power = 1; + String handle = extractResult.getText().toLowerCase(); + Matcher match = config.getDigitalNumberRegex().matcher(handle); + int startIndex = 0; + while (match.find()) { + int tmpIndex = -1; + String matched = match.group(); + double rep = config.getRoundNumberMap().get(matched); + + // \\s+ for filter the spaces. + power *= rep; + + while ((tmpIndex = handle.indexOf(matched.toLowerCase(), startIndex)) >= 0) { + String front = QueryProcessor.trimEnd(handle.substring(0, tmpIndex)); + startIndex = front.length(); + handle = front + handle.substring(tmpIndex + matched.length()); + } + } + + // Scale used in the calculate of double + result.setValue(getDigitalValue(handle, power)); + + return result; + } + + private ParseResult fracLikeNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + String resultText = extractResult.getText().toLowerCase(); + + Matcher match = config.getFractionPrepositionRegex().matcher(resultText); + if (match.find()) { + + String numerator = match.group("numerator"); + String denominator = match.group("denominator"); + + double smallValue = Character.isDigit(numerator.charAt(0)) ? + getDigitalValue(numerator, 1) : + getIntValue(getMatches(numerator)); + + double bigValue = Character.isDigit(denominator.charAt(0)) ? + getDigitalValue(denominator, 1) : + getIntValue(getMatches(denominator)); + + result.setValue(smallValue / bigValue); + } else { + List fracWords = config.normalizeTokenSet(Arrays.asList(resultText.split(" ")), result); + + // Split fraction with integer + int splitIndex = fracWords.size() - 1; + long currentValue = config.resolveCompositeNumber(fracWords.get(splitIndex)); + long roundValue = 1; + + // For case like "half" + if (fracWords.size() == 1) { + result.setValue(1 / getIntValue(fracWords)); + return result; + } + + for (splitIndex = fracWords.size() - 2; splitIndex >= 0; splitIndex--) { + + String fracWord = fracWords.get(splitIndex); + if (config.getWrittenFractionSeparatorTexts().contains(fracWord) || + config.getWrittenIntegerSeparatorTexts().contains(fracWord)) { + continue; + } + + long previousValue = currentValue; + currentValue = config.resolveCompositeNumber(fracWord); + + int smHundreds = 100; + + // previous : hundred + // current : one + if ((previousValue >= smHundreds && previousValue > currentValue) || + (previousValue < smHundreds && isComposable(currentValue, previousValue))) { + if (previousValue < smHundreds && currentValue >= roundValue) { + roundValue = currentValue; + } else if (previousValue < smHundreds && currentValue < roundValue) { + splitIndex++; + break; + } + + // current is the first word + if (splitIndex == 0) { + // scan, skip the first word + splitIndex = 1; + while (splitIndex <= fracWords.size() - 2) { + // e.g. one hundred thousand + // frac[i+1] % 100 && frac[i] % 100 = 0 + if (config.resolveCompositeNumber(fracWords.get(splitIndex)) >= smHundreds && + !config.getWrittenFractionSeparatorTexts().contains(fracWords.get(splitIndex + 1)) && + config.resolveCompositeNumber(fracWords.get(splitIndex + 1)) < smHundreds) { + splitIndex++; + break; + } + splitIndex++; + } + break; + } + continue; + } + splitIndex++; + break; + } + + if (splitIndex < 0) { + splitIndex = 0; + } + + List fracPart = new ArrayList(); + for (int i = splitIndex; i < fracWords.size(); i++) { + if (fracWords.get(i).contains("-")) { + String[] split = fracWords.get(i).split(Pattern.quote("-")); + fracPart.add(split[0]); + fracPart.add("-"); + fracPart.add(split[1]); + } else { + fracPart.add(fracWords.get(i)); + } + } + + fracWords.subList(splitIndex, fracWords.size()).clear(); + + // denomi = denominator + double denomiValue = getIntValue(fracPart); + // Split mixed number with fraction + double numerValue = 0; + double intValue = 0; + + int mixedIndex = fracWords.size(); + for (int i = fracWords.size() - 1; i >= 0; i--) { + if (i < fracWords.size() - 1 && config.getWrittenFractionSeparatorTexts().contains(fracWords.get(i))) { + String numerStr = String.join(" ", fracWords.subList(i + 1, fracWords.size())); + numerValue = getIntValue(getMatches(numerStr)); + mixedIndex = i + 1; + break; + } + } + + String intStr = String.join(" ", fracWords.subList(0, mixedIndex)); + intValue = getIntValue(getMatches(intStr)); + + // Find mixed number + if (mixedIndex != fracWords.size() && numerValue < denomiValue) { + result.setValue(intValue + numerValue / denomiValue); + } else { + result.setValue((intValue + numerValue) / denomiValue); + } + } + + return result; + } + + private ParseResult textNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + String handle = extractResult.getText().toLowerCase(); + + //region Special case for "dozen" + handle = config.getHalfADozenRegex().matcher(handle).replaceAll(config.getHalfADozenText()); + //endregion + + List numGroup = QueryProcessor.split(handle, config.getWrittenDecimalSeparatorTexts()); + + //region IntegerPart + String intPart = numGroup.get(0); + + //Store all match str. + List matchStrs = new ArrayList<>(); + Matcher smatch = textNumberRegex.matcher(intPart); + while (smatch.find()) { + String matchStr = smatch.group().toLowerCase(); + matchStrs.add(matchStr); + } + + // Get the value recursively + double intPartRet = getIntValue(matchStrs); + //endregion + + //region DecimalPart + double pointPartRet = 0; + if (numGroup.size() == 2) { + String pointPart = numGroup.get(1); + smatch = textNumberRegex.matcher(pointPart); + matchStrs.clear(); + while (smatch.find()) { + String matchStr = smatch.group().toLowerCase(); + matchStrs.add(matchStr); + } + pointPartRet += getPointValue(matchStrs); + } + //endregion + + result.setValue(intPartRet + pointPartRet); + + return result; + } + + protected ParseResult powerNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + + String handle = extractResult.getText().toUpperCase(); + boolean isE = !extractResult.getText().contains("^"); + + //[1] 1e10 + //[2] 1.1^-23 + Stack calStack = new Stack<>(); + + double scale = 10; + boolean dot = false; + boolean isNegative = false; + double tmp = 0; + for (int i = 0; i < handle.length(); i++) { + char ch = handle.charAt(i); + if (ch == '^' || ch == 'E') { + if (isNegative) { + calStack.add(-tmp); + } else { + calStack.add(tmp); + } + tmp = 0; + scale = 10; + dot = false; + isNegative = false; + } else if (ch >= '0' && ch <= '9') { + if (dot) { + tmp = tmp + scale * (ch - '0'); + scale *= 0.1; + } else { + tmp = tmp * scale + (ch - '0'); + } + } else if (ch == config.getDecimalSeparatorChar()) { + dot = true; + scale = 0.1; + } else if (ch == '-') { + isNegative = !isNegative; + } else if (ch == '+') { + continue; + } + + if (i == handle.length() - 1) { + if (isNegative) { + calStack.add(-tmp); + } else { + calStack.add(tmp); + } + } + } + + double ret; + if (isE) { + ret = calStack.remove(0) * Math.pow(10, calStack.remove(0)); + } else { + ret = Math.pow(calStack.remove(0), calStack.remove(0)); + } + + + result.setValue(ret); + result.setResolutionStr(NumberFormatUtility.format(ret, config.getCultureInfo())); + + return result; + } + + protected String getKeyRegex(Set keyCollection) { + ArrayList keys = new ArrayList<>(keyCollection); + Collections.sort(keys, Collections.reverseOrder()); + + return String.join("|", keys); + } + + private boolean skipNonDecimalSeparator(char ch, int distance, CultureInfo culture) { + int decimalLength = 3; + + // @TODO: Add this to project level configuration file to be kept in sync + // Special cases for multi-language countries where decimal separators can be used interchangeably. Mostly informally. + // Ex: South Africa, Namibia; Puerto Rico in ES; or in Canada for EN and FR. + // "me pidio $5.00 prestados" and "me pidio $5,00 prestados" -> currency $5 + Pattern cultureRegex = Pattern.compile("^(en|es|fr)(-)?\\b", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + return (ch == config.getNonDecimalSeparatorChar() && !(distance <= decimalLength && cultureRegex.matcher(culture.cultureCode).find())); + } + + protected double getDigitalValue(String digitsStr, double power) { + double temp = 0; + double scale = 10; + boolean decimalSeparator = false; + int strLength = digitsStr.length(); + boolean isNegative = false; + boolean isFrac = digitsStr.contains("/"); + + Stack calStack = new Stack<>(); + + for (int i = 0; i < digitsStr.length(); i++) { + + char ch = digitsStr.charAt(i); + boolean skippableNonDecimal = skipNonDecimalSeparator(ch, strLength - i, config.getCultureInfo()); + + if (!isFrac && (ch == ' ' || ch == Constants.NO_BREAK_SPACE || skippableNonDecimal)) { + continue; + } + + if (ch == ' ' || ch == '/') { + calStack.push(temp); + temp = 0; + } else if (ch >= '0' && ch <= '9') { + if (decimalSeparator) { + temp = temp + scale * (ch - '0'); + scale *= 0.1; + } else { + temp = temp * scale + (ch - '0'); + } + } else if (ch == config.getDecimalSeparatorChar() || (!skippableNonDecimal && ch == config.getNonDecimalSeparatorChar())) { + decimalSeparator = true; + scale = 0.1; + } else if (ch == '-') { + isNegative = true; + } + } + calStack.push(temp); + + // is the number is a fraction. + double calResult = 0; + if (isFrac) { + double deno = calStack.pop(); + double mole = calStack.pop(); + calResult += mole / deno; + } + + while (!calStack.empty()) { + calResult += calStack.pop(); + } + calResult *= power; + + if (isNegative) { + return -calResult; + } + + return calResult; + } + + + protected Double getIntValue(List matchStrs) { + boolean[] isEnd = new boolean[matchStrs.size()]; + Arrays.fill(isEnd, false); + + double tempValue = 0; + long endFlag = 1; + + //Scan from end to start, find the end word + for (int i = matchStrs.size() - 1; i >= 0; i--) { + if (roundNumberSet.contains(matchStrs.get(i))) { + //if false,then continue + //You will meet hundred first, then thousand. + if (endFlag > config.getRoundNumberMap().get(matchStrs.get(i))) { + continue; + } + + isEnd[i] = true; + endFlag = config.getRoundNumberMap().get(matchStrs.get(i)); + } + } + + if (endFlag == 1) { + Stack tempStack = new Stack<>(); + String oldSym = ""; + for (String matchStr : matchStrs) { + boolean isCardinal = config.getCardinalNumberMap().containsKey(matchStr); + boolean isOrdinal = config.getOrdinalNumberMap().containsKey(matchStr); + + if (isCardinal || isOrdinal) { + double matchValue = isCardinal ? + config.getCardinalNumberMap().get(matchStr) : + config.getOrdinalNumberMap().get(matchStr); + + //This is just for ordinal now. Not for fraction ever. + if (isOrdinal) { + double fracPart = config.getOrdinalNumberMap().get(matchStr); + if (!tempStack.empty()) { + double intPart = tempStack.pop(); + + // if intPart >= fracPart, it means it is an ordinal number + // it begins with an integer, ends with an ordinal + // e.g. ninety-ninth + if (intPart >= fracPart) { + tempStack.push(intPart + fracPart); + } else { + // another case of the type is ordinal + // e.g. three hundredth + while (!tempStack.empty()) { + intPart = intPart + tempStack.pop(); + } + tempStack.push(intPart * fracPart); + } + } else { + tempStack.push(fracPart); + } + } else if (config.getCardinalNumberMap().containsKey(matchStr)) { + if (oldSym.equalsIgnoreCase("-")) { + double sum = tempStack.pop() + matchValue; + tempStack.push(sum); + } else if (oldSym.equalsIgnoreCase(config.getWrittenIntegerSeparatorTexts().get(0)) || tempStack.size() < 2) { + tempStack.push(matchValue); + } else if (tempStack.size() >= 2) { + double sum = tempStack.pop() + matchValue; + sum = tempStack.pop() + sum; + tempStack.push(sum); + } + } + } else { + long complexValue = config.resolveCompositeNumber(matchStr); + if (complexValue != 0) { + tempStack.push((double)complexValue); + } + } + oldSym = matchStr; + } + + for (double stackValue : tempStack) { + tempValue += stackValue; + } + } else { + int lastIndex = 0; + double mulValue = 1; + double partValue = 1; + for (int i = 0; i < isEnd.length; i++) { + if (isEnd[i]) { + mulValue = config.getRoundNumberMap().get(matchStrs.get(i)); + partValue = 1; + + if (i != 0) { + partValue = getIntValue(matchStrs.subList(lastIndex, i)); + + } + + tempValue += mulValue * partValue; + lastIndex = i + 1; + } + } + + //Calculate the part like "thirty-one" + mulValue = 1; + if (lastIndex != isEnd.length) { + partValue = getIntValue(matchStrs.subList(lastIndex, isEnd.length)); + tempValue += mulValue * partValue; + } + } + + return tempValue; + } + + private double getPointValue(List matchStrs) { + double ret = 0; + String firstMatch = matchStrs.get(0); + + if (config.getCardinalNumberMap().containsKey(firstMatch) && config.getCardinalNumberMap().get(firstMatch) >= 10) { + String prefix = "0."; + int tempInt = getIntValue(matchStrs).intValue(); + String all = prefix + tempInt; + ret = Double.parseDouble(all); + } else { + double scale = 0.1; + for (String matchStr : matchStrs) { + ret += config.getCardinalNumberMap().get(matchStr) * scale; + scale *= 0.1; + } + } + + return ret; + } + + private List getMatches(String input) { + Matcher smatch = textNumberRegex.matcher(input); + List matchStrs = new ArrayList(); + + //Store all match str. + while (smatch.find()) { + String matchStr = smatch.group(); + matchStrs.add(matchStr); + } + + return matchStrs; + } + + //Test if big and combine with small. + //e.g. "hundred" can combine with "thirty" but "twenty" can't combine with "thirty". + private boolean isComposable(long big, long small) { + int baseNumber = small > 10 ? 100 : 10; + return big % baseNumber == 0 && big / baseNumber >= 1; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java new file mode 100644 index 000000000..6a2749422 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java @@ -0,0 +1,171 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.number.NumberOptions; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class BaseNumberParserConfiguration implements INumberParserConfiguration { + + private final String langMarker; + private final CultureInfo cultureInfo; + private final NumberOptions options; + + private final char nonDecimalSeparatorChar; + private final char decimalSeparatorChar; + private final String fractionMarkerToken; + private final String halfADozenText; + private final String wordSeparatorToken; + + private final List writtenDecimalSeparatorTexts; + private final List writtenGroupSeparatorTexts; + private final List writtenIntegerSeparatorTexts; + private final List writtenFractionSeparatorTexts; + + private final Map cardinalNumberMap; + private final Map ordinalNumberMap; + private final Map roundNumberMap; + private final Pattern halfADozenRegex; + private final Pattern digitalNumberRegex; + private final Pattern negativeNumberSignRegex; + private final Pattern fractionPrepositionRegex; + + private final boolean isCompoundNumberLanguage; + private final boolean isMultiDecimalSeparatorCulture; + + protected BaseNumberParserConfiguration(String langMarker, CultureInfo cultureInfo, boolean isCompoundNumberLanguage, boolean isMultiDecimalSeparatorCulture, + NumberOptions options, char nonDecimalSeparatorChar, char decimalSeparatorChar, + String fractionMarkerToken, String halfADozenText, String wordSeparatorToken, List writtenDecimalSeparatorTexts, List writtenGroupSeparatorTexts, + List writtenIntegerSeparatorTexts, List writtenFractionSeparatorTexts, Map cardinalNumberMap, Map ordinalNumberMap, + Map roundNumberMap, Pattern halfADozenRegex, Pattern digitalNumberRegex, Pattern negativeNumberSignRegex, Pattern fractionPrepositionRegex) { + + this.langMarker = langMarker; + this.cultureInfo = cultureInfo; + this.isCompoundNumberLanguage = isCompoundNumberLanguage; + this.isMultiDecimalSeparatorCulture = isMultiDecimalSeparatorCulture; + this.options = options; + this.nonDecimalSeparatorChar = nonDecimalSeparatorChar; + this.decimalSeparatorChar = decimalSeparatorChar; + this.fractionMarkerToken = fractionMarkerToken; + this.halfADozenText = halfADozenText; + this.wordSeparatorToken = wordSeparatorToken; + this.writtenDecimalSeparatorTexts = writtenDecimalSeparatorTexts; + this.writtenGroupSeparatorTexts = writtenGroupSeparatorTexts; + this.writtenIntegerSeparatorTexts = writtenIntegerSeparatorTexts; + this.writtenFractionSeparatorTexts = writtenFractionSeparatorTexts; + this.cardinalNumberMap = cardinalNumberMap; + this.ordinalNumberMap = ordinalNumberMap; + this.roundNumberMap = roundNumberMap; + this.halfADozenRegex = halfADozenRegex; + this.digitalNumberRegex = digitalNumberRegex; + this.negativeNumberSignRegex = negativeNumberSignRegex; + this.fractionPrepositionRegex = fractionPrepositionRegex; + } + + @Override + public Map getCardinalNumberMap() { + return this.cardinalNumberMap; + } + + @Override + public Map getOrdinalNumberMap() { + return this.ordinalNumberMap; + } + + @Override + public Map getRoundNumberMap() { + return this.roundNumberMap; + } + + @Override + public NumberOptions getOptions() { + return this.options; + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public Pattern getDigitalNumberRegex() { + return this.digitalNumberRegex; + } + + @Override + public Pattern getFractionPrepositionRegex() { + return this.fractionPrepositionRegex; + } + + @Override + public String getFractionMarkerToken() { + return this.fractionMarkerToken; + } + + @Override + public Pattern getHalfADozenRegex() { + return this.halfADozenRegex; + } + + @Override + public String getHalfADozenText() { + return this.halfADozenText; + } + + @Override + public String getLangMarker() { + return this.langMarker; + } + + @Override + public char getNonDecimalSeparatorChar() { + return this.nonDecimalSeparatorChar; + } + + @Override + public char getDecimalSeparatorChar() { + return this.decimalSeparatorChar; + } + + @Override + public String getWordSeparatorToken() { + return this.wordSeparatorToken; + } + + @Override + public List getWrittenDecimalSeparatorTexts() { + return this.writtenDecimalSeparatorTexts; + } + + @Override + public List getWrittenGroupSeparatorTexts() { + return this.writtenGroupSeparatorTexts; + } + + @Override + public List getWrittenIntegerSeparatorTexts() { + return this.writtenIntegerSeparatorTexts; + } + + @Override + public List getWrittenFractionSeparatorTexts() { + return this.writtenFractionSeparatorTexts; + } + + @Override + public Pattern getNegativeNumberSignRegex() { + return this.negativeNumberSignRegex; + } + + @Override + public boolean isCompoundNumberLanguage() { + return this.isCompoundNumberLanguage; + } + + @Override + public boolean isMultiDecimalSeparatorCulture() { + return this.isMultiDecimalSeparatorCulture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java new file mode 100644 index 000000000..890c0ddac --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java @@ -0,0 +1,224 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberRangeConstants; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +public class BaseNumberRangeParser implements IParser { + + protected final INumberRangeParserConfiguration config; + + public BaseNumberRangeParser(INumberRangeParserConfiguration config) { + this.config = config; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + + ParseResult ret = null; + + if (extractResult.getData() != null && !extractResult.getData().toString().isEmpty()) { + String type = extractResult.getData().toString(); + if (type.contains(NumberRangeConstants.TWONUM)) { + ret = parseNumberRangeWhichHasTwoNum(extractResult); + } else { + ret = parseNumberRangeWhichHasOneNum(extractResult); + } + } + + return ret; + } + + private ParseResult parseNumberRangeWhichHasTwoNum(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + List er = config.getNumberExtractor().extract(extractResult.getText()); + + // Valid extracted results for this type should have two numbers + if (er.size() != 2) { + er = config.getOrdinalExtractor().extract(extractResult.getText()); + + if (er.size() != 2) { + return result; + } + } + + List nums = er.stream().map(r -> { + Object value = config.getNumberParser().parse(r).getValue(); + return value == null ? 0 : (Double)value; + }).collect(Collectors.toList()); + + double startValue; + double endValue; + if (nums.get(0) < nums.get(1)) { + startValue = nums.get(0); + endValue = nums.get(1); + } else { + startValue = nums.get(1); + endValue = nums.get(0); + } + + String startValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(startValue, config.getCultureInfo()) : String.valueOf(startValue); + String endValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(endValue, config.getCultureInfo()) : String.valueOf(endValue); + + char leftBracket; + char rightBracket; + + String type = (String)extractResult.getData(); + if (type.contains(NumberRangeConstants.TWONUMBETWEEN)) { + // between 20 and 30: (20,30) + leftBracket = NumberRangeConstants.LEFT_OPEN; + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } else if (type.contains(NumberRangeConstants.TWONUMTILL)) { + // 20~30: [20,30) + leftBracket = NumberRangeConstants.LEFT_CLOSED; + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } else { + // check whether it contains string like "more or equal", "less or equal", "at least", etc. + Matcher match = config.getMoreOrEqual().matcher(extractResult.getText()); + boolean matches = match.find(); + if (!matches) { + match = config.getMoreOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + leftBracket = NumberRangeConstants.LEFT_CLOSED; + } else { + leftBracket = NumberRangeConstants.LEFT_OPEN; + } + + match = config.getLessOrEqual().matcher(extractResult.getText()); + matches = match.find(); + + if (!matches) { + match = config.getLessOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + rightBracket = NumberRangeConstants.RIGHT_CLOSED; + } else { + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } + } + + result.setValue(ImmutableMap.of( + "StartValue", startValue, + "EndValue", endValue)); + result.setResolutionStr(new StringBuilder() + .append(leftBracket) + .append(startValueStr) + .append(NumberRangeConstants.INTERVAL_SEPARATOR) + .append(endValueStr) + .append(rightBracket).toString()); + + return result; + } + + private ParseResult parseNumberRangeWhichHasOneNum(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + + List er = config.getNumberExtractor().extract(extractResult.getText()); + + // Valid extracted results for this type should have one number + if (er.size() != 1) { + er = config.getOrdinalExtractor().extract(extractResult.getText()); + + if (er.size() != 1) { + return result; + } + } + + List nums = er.stream().map(r -> { + Object value = config.getNumberParser().parse(r).getValue(); + return value == null ? 0 : (Double)value; + }).collect(Collectors.toList()); + + char leftBracket; + char rightBracket; + String startValueStr = ""; + String endValueStr = ""; + + String type = (String)extractResult.getData(); + if (type.contains(NumberRangeConstants.MORE)) { + rightBracket = NumberRangeConstants.RIGHT_OPEN; + + Matcher match = config.getMoreOrEqual().matcher(extractResult.getText()); + boolean matches = match.find(); + + if (!matches) { + match = config.getMoreOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (!matches) { + match = config.getMoreOrEqualSeparate().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + leftBracket = NumberRangeConstants.LEFT_CLOSED; + } else { + leftBracket = NumberRangeConstants.LEFT_OPEN; + } + + startValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(nums.get(0), config.getCultureInfo()) : nums.get(0).toString(); + + result.setValue(ImmutableMap.of("StartValue", nums.get(0))); + } else if (type.contains(NumberRangeConstants.LESS)) { + leftBracket = NumberRangeConstants.LEFT_OPEN; + + Matcher match = config.getLessOrEqual().matcher(extractResult.getText()); + boolean matches = match.find(); + + if (!matches) { + match = config.getLessOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (!matches) { + match = config.getLessOrEqualSeparate().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + rightBracket = NumberRangeConstants.RIGHT_CLOSED; + } else { + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } + + endValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(nums.get(0), config.getCultureInfo()) : nums.get(0).toString(); + + result.setValue(ImmutableMap.of("EndValue", nums.get(0))); + } else { + leftBracket = NumberRangeConstants.LEFT_CLOSED; + rightBracket = NumberRangeConstants.RIGHT_CLOSED; + + startValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(nums.get(0), config.getCultureInfo()) : nums.get(0).toString(); + endValueStr = startValueStr; + + result.setValue(ImmutableMap.of( + "StartValue", nums.get(0), + "EndValue", nums.get(0) + )); + } + + result.setResolutionStr(new StringBuilder() + .append(leftBracket) + .append(startValueStr) + .append(NumberRangeConstants.INTERVAL_SEPARATOR) + .append(endValueStr) + .append(rightBracket) + .toString()); + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java new file mode 100644 index 000000000..6ef02249e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java @@ -0,0 +1,67 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import java.util.List; +import org.javatuples.Pair; + +public class BasePercentageParser extends BaseNumberParser { + public BasePercentageParser(INumberParserConfiguration config) { + super(config); + } + + @Override + @SuppressWarnings("unchecked") + public ParseResult parse(ExtractResult extractResult) { + String originText = extractResult.getText(); + ParseResult ret = null; + + // replace text & data from extended info + if (extractResult.getData() instanceof List) { + List> extendedData = (List>)extractResult.getData(); + if (extendedData.size() == 2) { + // for case like "2 out of 5". + String newText = extendedData.get(0).getValue0() + " " + config.getFractionMarkerToken() + " " + extendedData.get(1).getValue0(); + extractResult.setText(newText); + extractResult.setData("Frac" + config.getLangMarker()); + + ret = super.parse(extractResult); + ret.setValue((double)ret.getValue() * 100); + } else if (extendedData.size() == 1) { + // for case like "one third of". + extractResult.setText(extendedData.get(0).getValue0()); + extractResult.setData(extendedData.get(0).getValue1().getData()); + + ret = super.parse(extractResult); + + if (extractResult.getData().toString().startsWith("Frac")) { + ret.setValue((double)ret.getValue() * 100); + } + } + + String resolutionStr = config.getCultureInfo() != null ? + NumberFormatUtility.format(ret.getValue(), config.getCultureInfo()) + "%" : + ret.getValue() + "%"; + ret.setResolutionStr(resolutionStr); + } else { + // for case like "one percent" or "1%". + Pair extendedData = (Pair)extractResult.getData(); + extractResult.setText(extendedData.getValue0()); + extractResult.setData(extendedData.getValue1().getData()); + + ret = super.parse(extractResult); + + if (ret.getResolutionStr() != null && !ret.getResolutionStr().isEmpty()) { + if (!ret.getResolutionStr().trim().endsWith("%")) { + ret.setResolutionStr(ret.getResolutionStr().trim() + "%"); + } + } + } + + ret.setText(originText); + ret.setData(extractResult.getText()); + + return ret; + + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java new file mode 100644 index 000000000..627792a5e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java @@ -0,0 +1,57 @@ +package com.microsoft.recognizers.text.number.parsers; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface ICJKNumberParserConfiguration extends INumberParserConfiguration { + //region language dictionaries + + Map getZeroToNineMap(); + + Map getRoundNumberMapChar(); + + Map getFullToHalfMap(); + + Map getUnitMap(); + + Map getTratoSimMap(); + + //endregion + + //region language lists + + List getRoundDirectList(); + + List getTenChars(); + + //endregion + + //region language settings + + Pattern getFracSplitRegex(); + + Pattern getDigitNumRegex(); + + Pattern getSpeGetNumberRegex(); + + Pattern getPercentageRegex(); + + Pattern getPercentageNumRegex(); + + Pattern getPointRegex(); + + Pattern getDoubleAndRoundRegex(); + + Pattern getPairRegex(); + + Pattern getDozenRegex(); + + Pattern getRoundNumberIntegerRegex(); + + char getZeroChar(); + + char getPairChar(); + + //endregion +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java new file mode 100644 index 000000000..22d31b927 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java @@ -0,0 +1,79 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface INumberParserConfiguration { + + //region language dictionaries + + Map getCardinalNumberMap(); + + Map getOrdinalNumberMap(); + + Map getRoundNumberMap(); + + //endregion + + //region language settings + + NumberOptions getOptions(); + + CultureInfo getCultureInfo(); + + Pattern getDigitalNumberRegex(); + + Pattern getFractionPrepositionRegex(); + + String getFractionMarkerToken(); + + Pattern getHalfADozenRegex(); + + String getHalfADozenText(); + + String getLangMarker(); + + char getNonDecimalSeparatorChar(); + + char getDecimalSeparatorChar(); + + String getWordSeparatorToken(); + + List getWrittenDecimalSeparatorTexts(); + + List getWrittenGroupSeparatorTexts(); + + List getWrittenIntegerSeparatorTexts(); + + List getWrittenFractionSeparatorTexts(); + + Pattern getNegativeNumberSignRegex(); + + boolean isCompoundNumberLanguage(); + + boolean isMultiDecimalSeparatorCulture(); + + //endregion + + /** + * Used when requiring to normalize a token to a valid expression supported by the ImmutableDictionaries (language dictionaries) + * + * @param tokens list of tokens to normalize + * @param context context of the call + * @return list of normalized tokens + */ + List normalizeTokenSet(List tokens, ParseResult context); + + /** + * Used when requiring to convert a String to a valid number supported by the language + * + * @param numberStr composite number + * @return value of the String + */ + long resolveCompositeNumber(String numberStr); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java new file mode 100644 index 000000000..9446a0b7c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.regex.Pattern; + +public interface INumberRangeParserConfiguration { + CultureInfo getCultureInfo(); + + IExtractor getNumberExtractor(); + + IExtractor getOrdinalExtractor(); + + IParser getNumberParser(); + + Pattern getMoreOrEqual(); + + Pattern getLessOrEqual(); + + Pattern getMoreOrEqualSuffix(); + + Pattern getLessOrEqualSuffix(); + + Pattern getMoreOrEqualSeparate(); + + Pattern getLessOrEqualSeparate(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java new file mode 100644 index 000000000..87c8dda11 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java @@ -0,0 +1,94 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + +public final class NumberFormatUtility { + + private NumberFormatUtility() { + } + + private static final Map supportedCultures; + + static { + supportedCultures = new HashMap<>(); + supportedCultures.put(Culture.English, LongFormatType.DoubleNumCommaDot); + supportedCultures.put(Culture.Spanish, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.Portuguese, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.French, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.German, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.Chinese, null); + supportedCultures.put(Culture.Japanese, LongFormatType.DoubleNumDotComma); + } + + public static String format(Object value, CultureInfo culture) { + + Double doubleValue = (Double)value; + String result; + + // EXPONENTIAL_AT: [-5, 15] }); + // For small positive decimal places. E.g.: 0,000015 or 0,0000015 -> 1.5E-05 or 1.5E-06 + if (doubleValue > 0 && doubleValue != Math.round(doubleValue) && doubleValue < 1E-4) { + result = doubleValue.toString(); + } else { + BigDecimal bc = new BigDecimal(doubleValue, new MathContext(15, RoundingMode.HALF_EVEN)); + result = bc.toString(); + } + + result = result.replace('e', 'E'); + if (result.contains("E-")) { + String[] parts = result.split(Pattern.quote("E-")); + parts[0] = QueryProcessor.trimEnd(parts[0], ".0"); + parts[1] = StringUtils.leftPad(parts[1], 2, '0'); + result = String.join("E-", parts); + } + + if (result.contains("E+")) { + String[] parts = result.split(Pattern.quote("E+")); + parts[0] = QueryProcessor.trimEnd(parts[0], "0"); + result = String.join("E+", parts); + } + + if (result.contains(".")) { + result = QueryProcessor.trimEnd(result, "0"); + result = QueryProcessor.trimEnd(result, "."); + } + + if (supportedCultures.containsKey(culture.cultureCode)) { + LongFormatType longFormat = supportedCultures.get(culture.cultureCode); + if (longFormat != null) { + Character[] chars = result.chars().mapToObj(i -> (char)i) + .map(c -> changeMark(c, longFormat)) + .toArray(Character[]::new); + + StringBuilder sb = new StringBuilder(chars.length); + for (Character c : chars) { + sb.append(c.charValue()); + } + + result = sb.toString(); + } + } + + return result; + } + + private static Character changeMark(Character c, LongFormatType longFormat) { + if (c == '.') { + return longFormat.decimalsMark; + } else if (c == ',') { + return longFormat.thousandsMark; + } + + return c; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java new file mode 100644 index 000000000..25d9c3f43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(PortugueseNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + // Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + // Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java new file mode 100644 index 000000000..31b97aae3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(PortugueseNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePor"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java new file mode 100644 index 000000000..c396deae8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS) , "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracPor"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS) , "FracPor"); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracPor"); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java new file mode 100644 index 000000000..f3edca18c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(PortugueseNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumDot, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithDozen2Suffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerPor"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerPor"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java new file mode 100644 index 000000000..eab23931c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java @@ -0,0 +1,104 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import static com.microsoft.recognizers.text.number.NumberMode.Default; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private NumberExtractor(NumberMode mode, NumberOptions options) { + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(PortugueseNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + // Add Fraction + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : PortugueseNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..089ea724b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java @@ -0,0 +1,35 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric. OrdinalEnglishRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalPor"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java new file mode 100644 index 000000000..7f65e04c5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(PortugueseNumeric.NumberWithSuffixPercentage); + this.regexes = buildRegexes(builder); + } + +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java new file mode 100644 index 000000000..a159335b3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java @@ -0,0 +1,142 @@ +package com.microsoft.recognizers.text.number.portuguese.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class PortugueseNumberParserConfiguration extends BaseNumberParserConfiguration { + + public PortugueseNumberParserConfiguration() { + this(NumberOptions.None); + } + + public PortugueseNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.Portuguese), options); + } + + public PortugueseNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + PortugueseNumeric.LangMarker, + cultureInfo, + PortugueseNumeric.CompoundNumberLanguage, + PortugueseNumeric.MultiDecimalSeparatorCulture, + options, + PortugueseNumeric.NonDecimalSeparatorChar, + PortugueseNumeric.DecimalSeparatorChar, + PortugueseNumeric.FractionMarkerToken, + PortugueseNumeric.HalfADozenText, + PortugueseNumeric.WordSeparatorToken, + PortugueseNumeric.WrittenDecimalSeparatorTexts, + PortugueseNumeric.WrittenGroupSeparatorTexts, + PortugueseNumeric.WrittenIntegerSeparatorTexts, + PortugueseNumeric.WrittenFractionSeparatorTexts, + PortugueseNumeric.CardinalNumberMap, + buildOrdinalNumberMap(), + PortugueseNumeric.RoundNumberMap, + + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.HalfADozenRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DigitalNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NegativeNumberSignRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS)); + } + + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + Map cardinalNumberMap = this.getCardinalNumberMap(); + Map ordinalNumberMap = this.getOrdinalNumberMap(); + + List result = new ArrayList<>(); + + for (String token : tokens) { + String tempWord = QueryProcessor.trimEnd(token, String.valueOf(PortugueseNumeric.PluralSuffix)); + if (ordinalNumberMap.containsKey(tempWord)) { + result.add(tempWord); + continue; + } + + // ends with 'avo' or 'ava' + String finalTempWord = tempWord; + if (PortugueseNumeric.WrittenFractionSuffix.stream().anyMatch(suffix -> finalTempWord.endsWith(suffix))) { + String origTempWord = tempWord; + int newLength = origTempWord.length(); + tempWord = origTempWord.substring(0, newLength - 3); + + if (tempWord.isEmpty()) { + // Ignore avos in fractions. + continue; + } else if (cardinalNumberMap.containsKey(tempWord)) { + result.add(tempWord); + continue; + } else { + tempWord = origTempWord.substring(0, newLength - 2); + if (cardinalNumberMap.containsKey(tempWord)) { + result.add(tempWord); + continue; + } + } + } + + result.add(token); + } + + return result; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + Map cardinalNumberMap = this.getCardinalNumberMap(); + Map ordinalNumberMap = this.getOrdinalNumberMap(); + if (ordinalNumberMap.containsKey(numberStr)) { + return ordinalNumberMap.get(numberStr); + } + + if (cardinalNumberMap.containsKey(numberStr)) { + return cardinalNumberMap.get(numberStr); + } + + long value = 0; + long finalValue = 0; + StringBuilder strBuilder = new StringBuilder(); + int lastGoodChar = 0; + for (int i = 0; i < numberStr.length(); i++) { + strBuilder.append(numberStr.charAt(i)); + String tmp = strBuilder.toString(); + if (cardinalNumberMap.containsKey(tmp) && cardinalNumberMap.get(tmp) > value) { + lastGoodChar = i; + value = cardinalNumberMap.get(tmp); + } + + if ((i + 1) == numberStr.length()) { + finalValue += value; + strBuilder = new StringBuilder(); + i = lastGoodChar++; + value = 0; + } + } + + return finalValue; + } + + private static Map buildOrdinalNumberMap() { + ImmutableMap.Builder builder = new ImmutableMap.Builder() + .putAll(PortugueseNumeric.OrdinalNumberMap); + + PortugueseNumeric.SuffixOrdinalMap.forEach((sufixKey, sufixValue) -> + PortugueseNumeric.PrefixCardinalMap.forEach((prefixKey, prefixValue) -> + builder.put(prefixKey + sufixKey, prefixValue * sufixValue))); + + return builder.build(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java new file mode 100644 index 000000000..bed1156a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import com.google.common.collect.ImmutableMap; + +public class BaseNumbers { + + public static final String NumberReplaceToken = "@builtin.num"; + + public static final String FractionNumberReplaceToken = "@builtin.num.fraction"; + + public static String IntegerRegexDefinition(String placeholder, String thousandsmark) { + return "(((? +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class ChineseNumeric { + + public static final String LangMarker = "Chi"; + + public static final Boolean CompoundNumberLanguage = true; + + public static final Boolean MultiDecimalSeparatorCulture = false; + + public static final Character DecimalSeparatorChar = '.'; + + public static final String FractionMarkerToken = ""; + + public static final Character NonDecimalSeparatorChar = ' '; + + public static final String HalfADozenText = ""; + + public static final String WordSeparatorToken = ""; + + public static final Character ZeroChar = '零'; + + public static final Character PairChar = '对'; + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMapChar = ImmutableMap.builder() + .put('十', 10L) + .put('百', 100L) + .put('千', 1000L) + .put('万', 10000L) + .put('亿', 100000000L) + .put('兆', 1000000000000L) + .put('拾', 10L) + .put('佰', 100L) + .put('仟', 1000L) + .put('萬', 10000L) + .put('億', 100000000L) + .build(); + + public static final ImmutableMap ZeroToNineMap = ImmutableMap.builder() + .put('零', 0D) + .put('一', 1D) + .put('二', 2D) + .put('三', 3D) + .put('四', 4D) + .put('五', 5D) + .put('六', 6D) + .put('七', 7D) + .put('八', 8D) + .put('九', 9D) + .put('〇', 0D) + .put('壹', 1D) + .put('贰', 2D) + .put('貳', 2D) + .put('叁', 3D) + .put('肆', 4D) + .put('伍', 5D) + .put('陆', 6D) + .put('陸', 6D) + .put('柒', 7D) + .put('捌', 8D) + .put('玖', 9D) + .put('0', 0D) + .put('1', 1D) + .put('2', 2D) + .put('3', 3D) + .put('4', 4D) + .put('5', 5D) + .put('6', 6D) + .put('7', 7D) + .put('8', 8D) + .put('9', 9D) + .put('0', 0D) + .put('1', 1D) + .put('2', 2D) + .put('3', 3D) + .put('4', 4D) + .put('5', 5D) + .put('6', 6D) + .put('7', 7D) + .put('8', 8D) + .put('9', 9D) + .put('半', 0.5D) + .put('两', 2D) + .put('兩', 2D) + .put('俩', 2D) + .put('倆', 2D) + .put('仨', 3D) + .build(); + + public static final ImmutableMap FullToHalfMap = ImmutableMap.builder() + .put('0', '0') + .put('1', '1') + .put('2', '2') + .put('3', '3') + .put('4', '4') + .put('5', '5') + .put('6', '6') + .put('7', '7') + .put('8', '8') + .put('9', '9') + .put('/', '/') + .put('-', '-') + .put(',', '\'') + .put('G', 'G') + .put('M', 'M') + .put('T', 'T') + .put('K', 'K') + .put('k', 'k') + .put('.', '.') + .build(); + + public static final ImmutableMap TratoSimMap = ImmutableMap.builder() + .put('佰', '百') + .put('點', '点') + .put('個', '个') + .put('幾', '几') + .put('對', '对') + .put('雙', '双') + .build(); + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("萬萬", "億") + .put("億萬", "兆") + .put("萬億", "兆") + .put("万万", "亿") + .put("万亿", "兆") + .put("亿万", "兆") + .put(" ", "") + .put("多", "") + .put("余", "") + .put("几", "") + .build(); + + public static final List RoundDirectList = Arrays.asList('亿', '兆', '億'); + + public static final List TenChars = Arrays.asList('十', '拾'); + + public static final String DigitalNumberRegex = "((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final String ZeroToNineFullHalfRegex = "[\\d1234567890]"; + + public static final String DigitNumRegex = "{ZeroToNineFullHalfRegex}+" + .replace("{ZeroToNineFullHalfRegex}", ZeroToNineFullHalfRegex); + + public static final String DozenRegex = ".*打$"; + + public static final String PercentageRegex = "(?<=(((?)"; + + public static final String LessRegex = "(小于|少于|低于|小於|少於|低於|不到|不足|<)"; + + public static final String EqualRegex = "(等于|等於|=)"; + + public static final String MoreOrEqual = "(({MoreRegex}\\s*(或|或者)?\\s*{EqualRegex})|(至少|最少){SpeicalCharBeforeNumber}?|不{LessRegex}|≥)" + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{SpeicalCharBeforeNumber}", SpeicalCharBeforeNumber); + + public static final String MoreOrEqualSuffix = "(或|或者)\\s*(以上|之上|更[大多高])"; + + public static final String LessOrEqual = "(({LessRegex}\\s*(或|或者)?\\s*{EqualRegex})|(至多|最多){SpeicalCharBeforeNumber}?|不{MoreRegex}|≤)" + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{SpeicalCharBeforeNumber}", SpeicalCharBeforeNumber); + + public static final String LessOrEqualSuffix = "(或|或者)\\s*(以下|之下|更[小少低])"; + + public static final String OneNumberRangeMoreRegex1 = "({MoreOrEqual}|{MoreRegex})\\s*(?((?!([并且而並的同時时]|([,,](?!\\d+))|。)).)+)" + .replace("{MoreOrEqual}", MoreOrEqual) + .replace("{MoreRegex}", MoreRegex); + + public static final String OneNumberRangeMoreRegex2 = "比\\s*(?((?!(([,,](?!\\d+))|。)).)+)\\s*更?[大多高]"; + + public static final String OneNumberRangeMoreRegex3 = "(?((?!(([,,](?!\\d+))|。|[或者])).)+)\\s*(或|或者)?\\s*([多几余幾餘]|以上|之上|更[大多高])([万亿萬億]{0,2})"; + + public static final String OneNumberRangeLessRegex1 = "({LessOrEqual}|{LessRegex})\\s*(?((?!([并且而並的同時时]|([,,](?!\\d+))|。)).)+)" + .replace("{LessOrEqual}", LessOrEqual) + .replace("{LessRegex}", LessRegex); + + public static final String OneNumberRangeLessRegex2 = "比\\s*(?((?!(([,,](?!\\d+))|。)).)+)\\s*更?[小少低]"; + + public static final String OneNumberRangeLessRegex3 = "(?((?!(([,,](?!\\d+))|。|[或者])).)+)\\s*(或|或者)?\\s*(以下|之下|更[小少低])"; + + public static final String OneNumberRangeMoreSeparateRegex = "^[.]"; + + public static final String OneNumberRangeLessSeparateRegex = "^[.]"; + + public static final String OneNumberRangeEqualRegex = "{EqualRegex}\\s*(?((?!(([,,](?!\\d+))|。)).)+)" + .replace("{EqualRegex}", EqualRegex); + + public static final String TwoNumberRangeRegex1 = "((位于|在|位於)|(?=(\\d|\\+|\\-)))\\s*(?((?!(([,,](?!\\d+))|。)).)+)\\s*(和|与|與|{TillRegex})\\s*(?((?!(([,,](?!\\d+))|。))[^之])+)\\s*(之)?(间|間)" + .replace("{TillRegex}", TillRegex); + + public static final String TwoNumberRangeRegex2 = "({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2}|{OneNumberRangeMoreRegex3})\\s*(且|(并|並)且?|而且|((的)?同時)|((的)?同时)|[,,])?\\s*({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2}|{OneNumberRangeLessRegex3})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeMoreRegex3}", OneNumberRangeMoreRegex3) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2) + .replace("{OneNumberRangeLessRegex3}", OneNumberRangeLessRegex3); + + public static final String TwoNumberRangeRegex3 = "({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2}|{OneNumberRangeLessRegex3})\\s*(且|(并|並)且?|而且|((的)?同時)|((的)?同时)|[,,])?\\s*({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2}|{OneNumberRangeMoreRegex3})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeMoreRegex3}", OneNumberRangeMoreRegex3) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2) + .replace("{OneNumberRangeLessRegex3}", OneNumberRangeLessRegex3); + + public static final String TwoNumberRangeRegex4 = "(?((?!(([,,](?!\\d+))|。)).)+)\\s*{TillRegex}\\s*(?((?!(([,,](?!\\d+))|。)).)+)" + .replace("{TillRegex}", TillRegex); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("十", "十足") + .put("伍", "队伍") + .put("肆", "放肆|肆意|肆无忌惮") + .put("陆", "大陆|陆地|登陆|海陆") + .put("拾", "拾取|拾起|收拾|拾到|朝花夕拾") + .build(); + + public static final String AmbiguousFractionConnectorsRegex = "^[.]"; + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java new file mode 100644 index 000000000..5283e4c40 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java @@ -0,0 +1,504 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishNumeric { + + public static final String LangMarker = "Eng"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = true; + + public static final String RoundNumberIntegerRegex = "(?:hundred|thousand|million|billion|trillion|lakh|crore)"; + + public static final String ZeroToNineIntegerRegex = "(?:three|seven|eight|four|five|zero|nine|one|two|six)"; + + public static final String TwoToNineIntegerRegex = "(?:three|seven|eight|four|five|nine|two|six)"; + + public static final String NegativeNumberTermsRegex = "(?(minus|negative)\\s+)"; + + public static final String NegativeNumberSignRegex = "^{NegativeNumberTermsRegex}.*" + .replace("{NegativeNumberTermsRegex}", NegativeNumberTermsRegex); + + public static final String AnIntRegex = "(an?)(?=\\s)"; + + public static final String TenToNineteenIntegerRegex = "(?:seventeen|thirteen|fourteen|eighteen|nineteen|fifteen|sixteen|eleven|twelve|ten)"; + + public static final String TensNumberIntegerRegex = "(?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)"; + + public static final String SeparaIntRegex = "(?:(({TenToNineteenIntegerRegex}|({TensNumberIntegerRegex}(\\s+(and\\s+)?|\\s*-\\s*){ZeroToNineIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex})(\\s+{RoundNumberIntegerRegex})*))|(({AnIntRegex}(\\s+{RoundNumberIntegerRegex})+))" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex); + + public static final String AllIntRegex = "(?:((({TenToNineteenIntegerRegex}|({TensNumberIntegerRegex}(\\s+(and\\s+)?|\\s*-\\s*){ZeroToNineIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex}|{AnIntRegex})(\\s+{RoundNumberIntegerRegex})+)\\s+(and\\s+)?)*{SeparaIntRegex})" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String PlaceHolderPureNumber = "\\b"; + + public static final String PlaceHolderDefault = "\\D|\\b"; + + public static String NumbersWithPlaceHolder(String placeholder) { + return "(((?(next|previous|current)\\s+one|(the\\s+second|next)\\s+to\\s+last|the\\s+one\\s+before\\s+the\\s+last(\\s+one)?|the\\s+last\\s+but\\s+one|(ante)?penultimate|last|next|previous|current)"; + + public static final String BasicOrdinalRegex = "({NumberOrdinalRegex}|{RelativeOrdinalRegex})" + .replace("{NumberOrdinalRegex}", NumberOrdinalRegex) + .replace("{RelativeOrdinalRegex}", RelativeOrdinalRegex); + + public static final String SuffixBasicOrdinalRegex = "(?:(((({TensNumberIntegerRegex}(\\s+(and\\s+)?|\\s*-\\s*){ZeroToNineIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex}|{AnIntRegex})(\\s+{RoundNumberIntegerRegex})+)\\s+(and\\s+)?)*({TensNumberIntegerRegex}(\\s+|\\s*-\\s*))?{BasicOrdinalRegex})" + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{BasicOrdinalRegex}", BasicOrdinalRegex); + + public static final String SuffixRoundNumberOrdinalRegex = "(?:({AllIntRegex}\\s+){RoundNumberOrdinalRegex})" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex); + + public static final String AllOrdinalRegex = "(?:{SuffixBasicOrdinalRegex}|{SuffixRoundNumberOrdinalRegex})" + .replace("{SuffixBasicOrdinalRegex}", SuffixBasicOrdinalRegex) + .replace("{SuffixRoundNumberOrdinalRegex}", SuffixRoundNumberOrdinalRegex); + + public static final String OrdinalSuffixRegex = "(?<=\\b)(?:(\\d*(1st|2nd|3rd|[4-90]th))|(1[1-2]th))(?=\\b)"; + + public static final String OrdinalNumericRegex = "(?<=\\b)(?:\\d{1,3}(\\s*,\\s*\\d{3})*\\s*th)(?=\\b)"; + + public static final String OrdinalRoundNumberRegex = "(?{RoundNumberIntegerRegex})$" + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String FractionNounRegex = "(?<=\\b)({AllIntRegex}\\s+(and\\s+)?)?(({AllIntRegex})(\\s+|\\s*-\\s*)((({AllOrdinalRegex})|({RoundNumberOrdinalRegex}))s|halves|quarters)((\\s+of\\s+a)?\\s+{RoundNumberIntegerRegex})?|(half(\\s+a)?|quarter(\\s+of\\s+a)?)\\s+{RoundNumberIntegerRegex})(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String FractionNounWithArticleRegex = "(?<=\\b)((({AllIntRegex}\\s+(and\\s+)?)?(an?|one)(\\s+|\\s*-\\s*)(?!\\bfirst\\b|\\bsecond\\b)(({AllOrdinalRegex})|({RoundNumberOrdinalRegex})|(half|quarter)(((\\s+of)?\\s+a)?\\s+{RoundNumberIntegerRegex})?))|(half))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String FractionPrepositionRegex = "(?({AllIntRegex})|((?in|out\\s+of))\\s+(?({AllIntRegex})|(\\d+)(?![\\.,]))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String FractionPrepositionWithinPercentModeRegex = "(?({AllIntRegex})|((?({AllIntRegex})|(\\d+)(?![\\.,]))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s+{ZeroToNineIntegerRegex})+|(\\s+{SeparaIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String AllFloatRegex = "{AllIntRegex}(\\s+point){AllPointRegex}" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static final String DoubleWithMultiplierRegex = "(((?and)"; + + public static final String NumberWithSuffixPercentage = "(?)"; + + public static final String LessRegex = "(?:(less|lower|smaller|fewer)(\\s+than)?|below|under|(?|=)<)"; + + public static final String EqualRegex = "(equal(s|ing)?(\\s+(to|than))?|(?)=)"; + + public static final String MoreOrEqualPrefix = "((no\\s+{LessRegex})|(at\\s+least))" + .replace("{LessRegex}", LessRegex); + + public static final String MoreOrEqual = "(?:({MoreRegex}\\s+(or)?\\s+{EqualRegex})|({EqualRegex}\\s+(or)?\\s+{MoreRegex})|{MoreOrEqualPrefix}(\\s+(or)?\\s+{EqualRegex})?|({EqualRegex}\\s+(or)?\\s+)?{MoreOrEqualPrefix}|>\\s*=|≥)" + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{MoreOrEqualPrefix}", MoreOrEqualPrefix); + + public static final String MoreOrEqualSuffix = "((and|or)\\s+(((more|greater|higher|larger|bigger)((?!\\s+than)|(\\s+than(?!((\\s+or\\s+equal\\s+to)?\\s*\\d+)))))|((over|above)(?!\\s+than))))"; + + public static final String LessOrEqualPrefix = "((no\\s+{MoreRegex})|(at\\s+most)|(up\\s+to))" + .replace("{MoreRegex}", MoreRegex); + + public static final String LessOrEqual = "(({LessRegex}\\s+(or)?\\s+{EqualRegex})|({EqualRegex}\\s+(or)?\\s+{LessRegex})|{LessOrEqualPrefix}(\\s+(or)?\\s+{EqualRegex})?|({EqualRegex}\\s+(or)?\\s+)?{LessOrEqualPrefix}|<\\s*=|≤)" + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{LessOrEqualPrefix}", LessOrEqualPrefix); + + public static final String LessOrEqualSuffix = "((and|or)\\s+(less|lower|smaller|fewer)((?!\\s+than)|(\\s+than(?!(\\s*\\d+)))))"; + + public static final String NumberSplitMark = "(?![,.](?!\\d+))(?!\\s*\\b(and\\s+({LessRegex}|{MoreRegex})|but|or|to)\\b)" + .replace("{MoreRegex}", MoreRegex) + .replace("{LessRegex}", LessRegex); + + public static final String MoreRegexNoNumberSucceed = "((bigger|greater|more|higher|larger)((?!\\s+than)|\\s+(than(?!(\\s*\\d+))))|(above|over)(?!(\\s*\\d+)))"; + + public static final String LessRegexNoNumberSucceed = "((less|lower|smaller|fewer)((?!\\s+than)|\\s+(than(?!(\\s*\\d+))))|(below|under)(?!(\\s*\\d+)))"; + + public static final String EqualRegexNoNumberSucceed = "(equal(s|ing)?((?!\\s+(to|than))|(\\s+(to|than)(?!(\\s*\\d+)))))"; + + public static final String OneNumberRangeMoreRegex1 = "({MoreOrEqual}|{MoreRegex})\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{MoreOrEqual}", MoreOrEqual) + .replace("{MoreRegex}", MoreRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreRegex1LB = "(?({NumberSplitMark}.)+)\\s*{MoreOrEqualSuffix}" + .replace("{MoreOrEqualSuffix}", MoreOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){MoreRegexNoNumberSucceed})|({MoreRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{MoreRegexNoNumberSucceed}", MoreRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1 = "({LessOrEqual}|{LessRegex})\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{LessOrEqual}", LessOrEqual) + .replace("{LessRegex}", LessRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1LB = "(?({NumberSplitMark}.)+)\\s*{LessOrEqualSuffix}" + .replace("{LessOrEqualSuffix}", LessOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){LessRegexNoNumberSucceed})|({LessRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{LessRegexNoNumberSucceed}", LessRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeEqualRegex = "(?({NumberSplitMark}.)+)" + .replace("{EqualRegex}", EqualRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex1 = "between\\s*(the\\s+)?(?({NumberSplitMark}.)+)\\s*and\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex2 = "({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})\\s*(and|but|,)\\s*({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex3 = "({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})\\s*(and|but|,)\\s*({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex4 = "(from\\s+)?(?({NumberSplitMark}(?!\\bfrom\\b).)+)\\s*{TillRegex}\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{TillRegex}", TillRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String AmbiguousFractionConnectorsRegex = "(\\bin\\b)"; + + public static final Character DecimalSeparatorChar = '.'; + + public static final String FractionMarkerToken = "over"; + + public static final Character NonDecimalSeparatorChar = ','; + + public static final String HalfADozenText = "six"; + + public static final String WordSeparatorToken = "and"; + + public static final List WrittenDecimalSeparatorTexts = Arrays.asList("point"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("punto"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("and"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("and"); + + public static final String HalfADozenRegex = "half\\s+a\\s+dozen"; + + public static final String DigitalNumberRegex = "((?<=\\b)(hundred|thousand|[mb]illion|trillion|lakh|crore|dozen(s)?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("a", 1L) + .put("zero", 0L) + .put("an", 1L) + .put("one", 1L) + .put("two", 2L) + .put("three", 3L) + .put("four", 4L) + .put("five", 5L) + .put("six", 6L) + .put("seven", 7L) + .put("eight", 8L) + .put("nine", 9L) + .put("ten", 10L) + .put("eleven", 11L) + .put("twelve", 12L) + .put("dozen", 12L) + .put("dozens", 12L) + .put("thirteen", 13L) + .put("fourteen", 14L) + .put("fifteen", 15L) + .put("sixteen", 16L) + .put("seventeen", 17L) + .put("eighteen", 18L) + .put("nineteen", 19L) + .put("twenty", 20L) + .put("thirty", 30L) + .put("forty", 40L) + .put("fifty", 50L) + .put("sixty", 60L) + .put("seventy", 70L) + .put("eighty", 80L) + .put("ninety", 90L) + .put("hundred", 100L) + .put("thousand", 1000L) + .put("million", 1000000L) + .put("billion", 1000000000L) + .put("trillion", 1000000000000L) + .put("lakh", 100000L) + .put("crore", 10000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("first", 1L) + .put("second", 2L) + .put("secondary", 2L) + .put("half", 2L) + .put("third", 3L) + .put("fourth", 4L) + .put("quarter", 4L) + .put("fifth", 5L) + .put("sixth", 6L) + .put("seventh", 7L) + .put("eighth", 8L) + .put("ninth", 9L) + .put("tenth", 10L) + .put("eleventh", 11L) + .put("twelfth", 12L) + .put("thirteenth", 13L) + .put("fourteenth", 14L) + .put("fifteenth", 15L) + .put("sixteenth", 16L) + .put("seventeenth", 17L) + .put("eighteenth", 18L) + .put("nineteenth", 19L) + .put("twentieth", 20L) + .put("thirtieth", 30L) + .put("fortieth", 40L) + .put("fiftieth", 50L) + .put("sixtieth", 60L) + .put("seventieth", 70L) + .put("eightieth", 80L) + .put("ninetieth", 90L) + .put("hundredth", 100L) + .put("thousandth", 1000L) + .put("millionth", 1000000L) + .put("billionth", 1000000000L) + .put("trillionth", 1000000000000L) + .put("firsts", 1L) + .put("halves", 2L) + .put("thirds", 3L) + .put("fourths", 4L) + .put("quarters", 4L) + .put("fifths", 5L) + .put("sixths", 6L) + .put("sevenths", 7L) + .put("eighths", 8L) + .put("ninths", 9L) + .put("tenths", 10L) + .put("elevenths", 11L) + .put("twelfths", 12L) + .put("thirteenths", 13L) + .put("fourteenths", 14L) + .put("fifteenths", 15L) + .put("sixteenths", 16L) + .put("seventeenths", 17L) + .put("eighteenths", 18L) + .put("nineteenths", 19L) + .put("twentieths", 20L) + .put("thirtieths", 30L) + .put("fortieths", 40L) + .put("fiftieths", 50L) + .put("sixtieths", 60L) + .put("seventieths", 70L) + .put("eightieths", 80L) + .put("ninetieths", 90L) + .put("hundredths", 100L) + .put("thousandths", 1000L) + .put("millionths", 1000000L) + .put("billionths", 1000000000L) + .put("trillionths", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("hundred", 100L) + .put("thousand", 1000L) + .put("million", 1000000L) + .put("billion", 1000000000L) + .put("trillion", 1000000000000L) + .put("lakh", 100000L) + .put("crore", 10000000L) + .put("hundredth", 100L) + .put("thousandth", 1000L) + .put("millionth", 1000000L) + .put("billionth", 1000000000L) + .put("trillionth", 1000000000000L) + .put("hundredths", 100L) + .put("thousandths", 1000L) + .put("millionths", 1000000L) + .put("billionths", 1000000000L) + .put("trillionths", 1000000000000L) + .put("dozen", 12L) + .put("dozens", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("mm", 1000000L) + .put("mil", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("\\bone\\b", "\\b(the|this|that|which)\\s+(one)\\b") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("last", "0") + .put("next one", "1") + .put("current", "0") + .put("current one", "0") + .put("previous one", "-1") + .put("the second to last", "-1") + .put("the one before the last one", "-1") + .put("the one before the last", "-1") + .put("next to last", "-1") + .put("penultimate", "-1") + .put("the last but one", "-1") + .put("antepenultimate", "-2") + .put("next", "1") + .put("previous", "-1") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("last", "end") + .put("next one", "current") + .put("previous one", "current") + .put("current", "current") + .put("current one", "current") + .put("the second to last", "end") + .put("the one before the last one", "end") + .put("the one before the last", "end") + .put("next to last", "end") + .put("penultimate", "end") + .put("the last but one", "end") + .put("antepenultimate", "end") + .put("next", "current") + .put("previous", "current") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java new file mode 100644 index 000000000..bbc5ae533 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java @@ -0,0 +1,508 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class FrenchNumeric { + + public static final String LangMarker = "Fre"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = true; + + public static final String RoundNumberIntegerRegex = "(cent|mille|millions?|milliards?|billions?)"; + + public static final String ZeroToNineIntegerRegex = "(une?|deux|trois|quatre|cinq|six|sept|huit|neuf)"; + + public static final String TenToNineteenIntegerRegex = "((seize|quinze|quatorze|treize|douze|onze)|dix(\\Wneuf|\\Whuit|\\Wsept)?)"; + + public static final String TensNumberIntegerRegex = "(quatre\\Wvingt(s|\\Wdix)?|soixante\\Wdix|vingt|trente|quarante|cinquante|soixante|septante|octante|huitante|nonante)"; + + public static final String DigitsNumberRegex = "\\d|\\d{1,3}(\\.\\d{3})"; + + public static final String NegativeNumberTermsRegex = "^[.]"; + + public static final String NegativeNumberSignRegex = "^({NegativeNumberTermsRegex}\\s+).*" + .replace("{NegativeNumberTermsRegex}", NegativeNumberTermsRegex); + + public static final String HundredsNumberIntegerRegex = "(({ZeroToNineIntegerRegex}(\\s+cent))|cent|((\\s+cent\\s)+{TensNumberIntegerRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex); + + public static final String BelowHundredsRegex = "(({TenToNineteenIntegerRegex}|({TensNumberIntegerRegex}((-|(\\s+et)?\\s+)({TenToNineteenIntegerRegex}|{ZeroToNineIntegerRegex}))?))|{ZeroToNineIntegerRegex})" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex); + + public static final String BelowThousandsRegex = "(({HundredsNumberIntegerRegex}(\\s+{BelowHundredsRegex})?|{BelowHundredsRegex}|{TenToNineteenIntegerRegex})|cent\\s+{TenToNineteenIntegerRegex})" + .replace("{HundredsNumberIntegerRegex}", HundredsNumberIntegerRegex) + .replace("{BelowHundredsRegex}", BelowHundredsRegex) + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex); + + public static final String SupportThousandsRegex = "(({BelowThousandsRegex}|{BelowHundredsRegex})\\s+{RoundNumberIntegerRegex}(\\s+{RoundNumberIntegerRegex})?)" + .replace("{BelowThousandsRegex}", BelowThousandsRegex) + .replace("{BelowHundredsRegex}", BelowHundredsRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String SeparaIntRegex = "({SupportThousandsRegex}(\\s+{SupportThousandsRegex})*(\\s+{BelowThousandsRegex})?|{BelowThousandsRegex})" + .replace("{SupportThousandsRegex}", SupportThousandsRegex) + .replace("{BelowThousandsRegex}", BelowThousandsRegex); + + public static final String AllIntRegex = "({SeparaIntRegex}|mille(\\s+{BelowThousandsRegex})?)" + .replace("{SeparaIntRegex}", SeparaIntRegex) + .replace("{BelowThousandsRegex}", BelowThousandsRegex); + + public static String NumbersWithPlaceHolder(String placeholder) { + return "(((?({AllIntRegex})|((?({AllIntRegex})|((\\d+)(?!\\.)))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s+{ZeroToNineIntegerRegex})+|(\\s+{SeparaIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String AllFloatRegex = "({AllIntRegex}(\\s+(virgule|point)){AllPointRegex})" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static String DoubleDecimalPointRegex(String placeholder) { + return "(((? WrittenDecimalSeparatorTexts = Arrays.asList("virgule"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("point", "points"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("et", "-"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("et", "sur"); + + public static final String HalfADozenRegex = "(?<=\\b)demie?\\s+douzaine"; + + public static final String DigitalNumberRegex = "((?<=\\b)(cent|mille|millions?|milliards?|billions?|douzaines?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final String AmbiguousFractionConnectorsRegex = "^[.]"; + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("zéro", 0L) + .put("zero", 0L) + .put("un", 1L) + .put("une", 1L) + .put("deux", 2L) + .put("trois", 3L) + .put("quatre", 4L) + .put("cinq", 5L) + .put("six", 6L) + .put("sept", 7L) + .put("huit", 8L) + .put("neuf", 9L) + .put("dix", 10L) + .put("onze", 11L) + .put("douze", 12L) + .put("douzaine", 12L) + .put("douzaines", 12L) + .put("treize", 13L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("seize", 16L) + .put("dix-sept", 17L) + .put("dix-huit", 18L) + .put("dix-huir", 18L) + .put("dix-neuf", 19L) + .put("vingt", 20L) + .put("trente", 30L) + .put("quarante", 40L) + .put("cinquante", 50L) + .put("soixante", 60L) + .put("soixante-dix", 70L) + .put("septante", 70L) + .put("quatre-vingts", 80L) + .put("quatre-vingt", 80L) + .put("quatre vingts", 80L) + .put("quatre vingt", 80L) + .put("quatre-vingt-dix", 90L) + .put("quatre-vingt dix", 90L) + .put("quatre vingt dix", 90L) + .put("quatre-vingts-dix", 90L) + .put("quatre-vingts-onze", 91L) + .put("quatre-vingt-onze", 91L) + .put("quatre-vingts-douze", 92L) + .put("quatre-vingt-douze", 92L) + .put("quatre-vingts-treize", 93L) + .put("quatre-vingt-treize", 93L) + .put("quatre-vingts-quatorze", 94L) + .put("quatre-vingt-quatorze", 94L) + .put("quatre-vingts-quinze", 95L) + .put("quatre-vingt-quinze", 95L) + .put("quatre-vingts-seize", 96L) + .put("quatre-vingt-seize", 96L) + .put("huitante", 80L) + .put("octante", 80L) + .put("nonante", 90L) + .put("cent", 100L) + .put("mille", 1000L) + .put("un million", 1000000L) + .put("million", 1000000L) + .put("millions", 1000000L) + .put("un milliard", 1000000000L) + .put("milliard", 1000000000L) + .put("milliards", 1000000000L) + .put("un mille milliards", 1000000000000L) + .put("un billion", 1000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("premier", 1L) + .put("première", 1L) + .put("premiere", 1L) + .put("unième", 1L) + .put("unieme", 1L) + .put("deuxième", 2L) + .put("deuxieme", 2L) + .put("second", 2L) + .put("seconde", 2L) + .put("troisième", 3L) + .put("demi", 2L) + .put("demie", 2L) + .put("tiers", 3L) + .put("tierce", 3L) + .put("quart", 4L) + .put("quarts", 4L) + .put("troisieme", 3L) + .put("quatrième", 4L) + .put("quatrieme", 4L) + .put("cinquième", 5L) + .put("cinquieme", 5L) + .put("sixième", 6L) + .put("sixieme", 6L) + .put("septième", 7L) + .put("septieme", 7L) + .put("huitième", 8L) + .put("huitieme", 8L) + .put("huirième", 8L) + .put("huirieme", 8L) + .put("neuvième", 9L) + .put("neuvieme", 9L) + .put("dixième", 10L) + .put("dixieme", 10L) + .put("dizième", 10L) + .put("dizieme", 10L) + .put("onzième", 11L) + .put("onzieme", 11L) + .put("douzième", 12L) + .put("douzieme", 12L) + .put("treizième", 13L) + .put("treizieme", 13L) + .put("quatorzième", 14L) + .put("quatorzieme", 14L) + .put("quinzième", 15L) + .put("quinzieme", 15L) + .put("seizième", 16L) + .put("seizieme", 16L) + .put("dix-septième", 17L) + .put("dix-septieme", 17L) + .put("dix-huitième", 18L) + .put("dix-huitieme", 18L) + .put("dix-huirième", 18L) + .put("dix-huirieme", 18L) + .put("dix-neuvième", 19L) + .put("dix-neuvieme", 19L) + .put("vingtième", 20L) + .put("vingtieme", 20L) + .put("trentième", 30L) + .put("trentieme", 30L) + .put("quarantième", 40L) + .put("quarantieme", 40L) + .put("cinquantième", 50L) + .put("cinquantieme", 50L) + .put("soixantième", 60L) + .put("soixantieme", 60L) + .put("soixante-dixième", 70L) + .put("soixante-dixieme", 70L) + .put("septantième", 70L) + .put("septantieme", 70L) + .put("quatre-vingtième", 80L) + .put("quatre-vingtieme", 80L) + .put("huitantième", 80L) + .put("huitantieme", 80L) + .put("octantième", 80L) + .put("octantieme", 80L) + .put("quatre-vingt-dixième", 90L) + .put("quatre-vingt-dixieme", 90L) + .put("nonantième", 90L) + .put("nonantieme", 90L) + .put("centième", 100L) + .put("centieme", 100L) + .put("millième", 1000L) + .put("millieme", 1000L) + .put("millionième", 1000000L) + .put("millionieme", 1000000L) + .put("milliardième", 1000000000L) + .put("milliardieme", 1000000000L) + .put("billionieme", 1000000000000L) + .put("billionième", 1000000000000L) + .put("trillionième", 1000000000000000000L) + .put("trillionieme", 1000000000000000000L) + .build(); + + public static final ImmutableMap PrefixCardinalMap = ImmutableMap.builder() + .put("deux", 2L) + .put("trois", 3L) + .put("quatre", 4L) + .put("cinq", 5L) + .put("six", 6L) + .put("sept", 7L) + .put("huit", 8L) + .put("neuf", 9L) + .put("dix", 10L) + .put("onze", 11L) + .put("douze", 12L) + .put("treize", 13L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("seize", 16L) + .put("dix sept", 17L) + .put("dix-sept", 17L) + .put("dix-huit", 18L) + .put("dix huit", 18L) + .put("dix-neuf", 19L) + .put("dix neuf", 19L) + .put("vingt", 20L) + .put("vingt-et-un", 21L) + .put("vingt et un", 21L) + .put("vingt-deux", 21L) + .put("vingt deux", 22L) + .put("vingt-trois", 23L) + .put("vingt trois", 23L) + .put("vingt-quatre", 24L) + .put("vingt quatre", 24L) + .put("vingt-cinq", 25L) + .put("vingt cinq", 25L) + .put("vingt-six", 26L) + .put("vingt six", 26L) + .put("vingt-sept", 27L) + .put("vingt sept", 27L) + .put("vingt-huit", 28L) + .put("vingt huit", 28L) + .put("vingt-neuf", 29L) + .put("vingt neuf", 29L) + .put("trente", 30L) + .put("quarante", 40L) + .put("cinquante", 50L) + .put("soixante", 60L) + .put("soixante-dix", 70L) + .put("soixante dix", 70L) + .put("septante", 70L) + .put("quatre-vingt", 80L) + .put("quatre vingt", 80L) + .put("huitante", 80L) + .put("octante", 80L) + .put("nonante", 90L) + .put("quatre vingt dix", 90L) + .put("quatre-vingt-dix", 90L) + .put("cent", 100L) + .put("deux cent", 200L) + .put("trois cents", 300L) + .put("quatre cents", 400L) + .put("cinq cent", 500L) + .put("six cent", 600L) + .put("sept cent", 700L) + .put("huit cent", 800L) + .put("neuf cent", 900L) + .build(); + + public static final ImmutableMap SuffixOrdinalMap = ImmutableMap.builder() + .put("millième", 1000L) + .put("million", 1000000L) + .put("milliardième", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("cent", 100L) + .put("mille", 1000L) + .put("million", 1000000L) + .put("millions", 1000000L) + .put("milliard", 1000000000L) + .put("milliards", 1000000000L) + .put("billion", 1000000000000L) + .put("billions", 1000000000000L) + .put("centieme", 100L) + .put("centième", 100L) + .put("millieme", 1000L) + .put("millième", 1000L) + .put("millionième", 1000000L) + .put("millionieme", 1000000L) + .put("milliardième", 1000000000L) + .put("milliardieme", 1000000000L) + .put("billionième", 1000000000000L) + .put("billionieme", 1000000000000L) + .put("centiemes", 100L) + .put("centièmes", 100L) + .put("millièmes", 1000L) + .put("milliemes", 1000L) + .put("millionièmes", 1000000L) + .put("millioniemes", 1000000L) + .put("milliardièmes", 1000000000L) + .put("milliardiemes", 1000000000L) + .put("billionièmes", 1000000000000L) + .put("billioniemes", 1000000000000L) + .put("douzaine", 12L) + .put("douzaines", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java new file mode 100644 index 000000000..f8cb70dd2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java @@ -0,0 +1,512 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class GermanNumeric { + + public static final String LangMarker = "Ger"; + + public static final Boolean CompoundNumberLanguage = true; + + public static final Boolean MultiDecimalSeparatorCulture = false; + + public static final String ZeroToNineIntegerRegex = "(drei|sieben|acht|vier|fuenf|fünf|null|neun|eins|(ein(?!($|\\.|,|!|\\?)))|eine|einer|einen|zwei|zwo|sechs)"; + + public static final String RoundNumberIntegerRegex = "(hundert|einhundert|tausend|(\\s*million\\s*)|(\\s*millionen\\s*)|(\\s*mio\\s*)|(\\s*milliarde\\s*)|(\\s*milliarden\\s*)|(\\s*mrd\\s*)|(\\s*billion\\s*)|(\\s*billionen\\s*))"; + + public static final String AnIntRegex = "(eine|ein)(?=\\s)"; + + public static final String TenToNineteenIntegerRegex = "(siebzehn|dreizehn|vierzehn|achtzehn|neunzehn|fuenfzehn|sechzehn|elf|zwoelf|zwölf|zehn)"; + + public static final String TensNumberIntegerRegex = "(siebzig|zwanzig|dreißig|achtzig|neunzig|vierzig|fuenfzig|fünfzig|sechzig)"; + + public static final String NegativeNumberTermsRegex = "^[.]"; + + public static final String NegativeNumberSignRegex = "^({NegativeNumberTermsRegex}\\s+).*" + .replace("{NegativeNumberTermsRegex}", NegativeNumberTermsRegex); + + public static final String SeparaIntRegex = "((({TenToNineteenIntegerRegex}|({ZeroToNineIntegerRegex}und{TensNumberIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex})(\\s*{RoundNumberIntegerRegex})*))|(({AnIntRegex}(\\s*{RoundNumberIntegerRegex})+))" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex); + + public static final String AllIntRegex = "(((({TenToNineteenIntegerRegex}|({ZeroToNineIntegerRegex}und{TensNumberIntegerRegex})|{TensNumberIntegerRegex}|({ZeroToNineIntegerRegex}|{AnIntRegex}))?(\\s*{RoundNumberIntegerRegex})))*{SeparaIntRegex})" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String PlaceHolderPureNumber = "\\b"; + + public static final String PlaceHolderDefault = "\\D|\\b"; + + public static String NumbersWithPlaceHolder(String placeholder) { + return "(((?anderthalb|einundhalb)|(?dreiviertel))"; + + public static final String FractionHalfRegex = "(einhalb)$"; + + public static final List OneHalfTokens = Arrays.asList("ein", "halb"); + + public static final String FractionNounRegex = "(?<=\\b)(({AllIntRegex})(\\s*|\\s*-\\s*)((({AllOrdinalRegex})|({RoundNumberOrdinalRegex}))|halb(er|e|es)?|hälfte)|{FractionUnitsRegex})(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{FractionUnitsRegex}", FractionUnitsRegex); + + public static final String FractionNounWithArticleRegex = "(?<=\\b)(({AllIntRegex}\\s+(und\\s+)?)?eine?(\\s+|\\s*-\\s*)({AllOrdinalRegex}|{RoundNumberOrdinalRegex}|{FractionUnitsRegex}|({AllIntRegex}ein)?(halb(er|e|es)?|hälfte))|{AllIntRegex}ein(halb))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{FractionUnitsRegex}", FractionUnitsRegex); + + public static final String FractionPrepositionRegex = "(?({AllIntRegex})|((?({AllIntRegex})|(\\d+)(?!\\.))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s*{ZeroToNineIntegerRegex})+|(\\s*{SeparaIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String AllFloatRegex = "({AllIntRegex}(\\s*komma\\s*){AllPointRegex})" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static final String DoubleWithMultiplierRegex = "(((? WrittenDecimalSeparatorTexts = Arrays.asList("komma"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("punkt"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("und"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("durch"); + + public static final String HalfADozenRegex = "ein\\s+halbes\\s+dutzend"; + + public static final String DigitalNumberRegex = "((?<=\\b)(hundert|tausend|million(en)?|mio|milliarde(n)?|mrd|billion(en)?|dutzend(e)?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("ein", 1L) + .put("null", 0L) + .put("eine", 1L) + .put("eins", 1L) + .put("einer", 1L) + .put("einen", 1L) + .put("beiden", 2L) + .put("zwei", 2L) + .put("zwo", 2L) + .put("drei", 3L) + .put("vier", 4L) + .put("fünf", 5L) + .put("fuenf", 5L) + .put("sechs", 6L) + .put("sieben", 7L) + .put("acht", 8L) + .put("neun", 9L) + .put("zehn", 10L) + .put("elf", 11L) + .put("zwölf", 12L) + .put("zwoelf", 12L) + .put("dutzend", 12L) + .put("dreizehn", 13L) + .put("vierzehn", 14L) + .put("fünfzehn", 15L) + .put("fuenfzehn", 15L) + .put("sechzehn", 16L) + .put("siebzehn", 17L) + .put("achtzehn", 18L) + .put("neunzehn", 19L) + .put("zwanzig", 20L) + .put("dreißig", 30L) + .put("vierzig", 40L) + .put("fünfzig", 50L) + .put("fuenfzig", 50L) + .put("sechzig", 60L) + .put("siebzig", 70L) + .put("achtzig", 80L) + .put("neunzig", 90L) + .put("hundert", 100L) + .put("tausend", 1000L) + .put("million", 1000000L) + .put("mio", 1000000L) + .put("millionen", 1000000L) + .put("milliard", 100000000L) + .put("milliarde", 1000000000L) + .put("mrd", 1000000000L) + .put("milliarden", 1000000000L) + .put("billion", 1000000000000L) + .put("billionen", 1000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("zuerst", 1L) + .put("erst", 1L) + .put("erster", 1L) + .put("erste", 1L) + .put("erstes", 1L) + .put("ersten", 1L) + .put("zweit", 2L) + .put("zweiter", 2L) + .put("zweite", 2L) + .put("zweites", 2L) + .put("zweiten", 2L) + .put("halb", 2L) + .put("halbe", 2L) + .put("halbes", 2L) + .put("hälfte", 2L) + .put("haelfte", 2L) + .put("dritt", 3L) + .put("dritter", 3L) + .put("dritte", 3L) + .put("drittes", 3L) + .put("dritten", 3L) + .put("drittel", 3L) + .put("viert", 4L) + .put("vierter", 4L) + .put("vierte", 4L) + .put("viertes", 4L) + .put("vierten", 4L) + .put("viertel", 4L) + .put("fünft", 5L) + .put("fünfter", 5L) + .put("fünfte", 5L) + .put("fünftes", 5L) + .put("fünften", 5L) + .put("fuenft", 5L) + .put("fuenfter", 5L) + .put("fuenfte", 5L) + .put("fuenftes", 5L) + .put("fuenften", 5L) + .put("fünftel", 5L) + .put("fuenftel", 5L) + .put("sechst", 6L) + .put("sechster", 6L) + .put("sechste", 6L) + .put("sechstes", 6L) + .put("sechsten", 6L) + .put("sechstel", 6L) + .put("siebt", 7L) + .put("siebter", 7L) + .put("siebte", 7L) + .put("siebtes", 7L) + .put("siebten", 7L) + .put("siebtel", 7L) + .put("acht", 8L) + .put("achter", 8L) + .put("achte", 8L) + .put("achtes", 8L) + .put("achten", 8L) + .put("achtel", 8L) + .put("neunt", 9L) + .put("neunter", 9L) + .put("neunte", 9L) + .put("neuntes", 9L) + .put("neunten", 9L) + .put("neuntel", 9L) + .put("zehnt", 10L) + .put("zehnter", 10L) + .put("zehnte", 10L) + .put("zehntes", 10L) + .put("zehnten", 10L) + .put("zehntel", 10L) + .put("elft", 11L) + .put("elfter", 11L) + .put("elfte", 11L) + .put("elftes", 11L) + .put("elften", 11L) + .put("elftel", 11L) + .put("zwölft", 12L) + .put("zwölfter", 12L) + .put("zwölfte", 12L) + .put("zwölftes", 12L) + .put("zwölften", 12L) + .put("zwoelft", 12L) + .put("zwoelfter", 12L) + .put("zwoelfte", 12L) + .put("zwoelftes", 12L) + .put("zwoelften", 12L) + .put("zwölftel", 12L) + .put("zwoelftel", 12L) + .put("dreizehnt", 13L) + .put("dreizehnter", 13L) + .put("dreizehnte", 13L) + .put("dreizehntes", 13L) + .put("dreizehnten", 13L) + .put("dreizehntel", 13L) + .put("vierzehnt", 14L) + .put("vierzehnter", 14L) + .put("vierzehnte", 14L) + .put("vierzehntes", 14L) + .put("vierzehnten", 14L) + .put("vierzehntel", 14L) + .put("fünfzehnt", 15L) + .put("fünfzehnter", 15L) + .put("fünfzehnte", 15L) + .put("fünfzehntes", 15L) + .put("fünfzehnten", 15L) + .put("fünfzehntel", 15L) + .put("fuenfzehnt", 15L) + .put("fuenfzehnter", 15L) + .put("fuenfzehnte", 15L) + .put("fuenfzehntes", 15L) + .put("fuenfzehnten", 15L) + .put("fuenfzehntel", 15L) + .put("sechzehnt", 16L) + .put("sechzehnter", 16L) + .put("sechzehnte", 16L) + .put("sechzehntes", 16L) + .put("sechzehnten", 16L) + .put("sechzehntel", 16L) + .put("siebzehnt", 17L) + .put("siebzehnter", 17L) + .put("siebzehnte", 17L) + .put("siebzehntes", 17L) + .put("siebzehnten", 17L) + .put("siebzehntel", 17L) + .put("achtzehnt", 18L) + .put("achtzehnter", 18L) + .put("achtzehnte", 18L) + .put("achtzehntes", 18L) + .put("achtzehnten", 18L) + .put("achtzehntel", 18L) + .put("neunzehnt", 19L) + .put("neunzehnter", 19L) + .put("neunzehnte", 19L) + .put("neunzehntes", 19L) + .put("neunzehnten", 19L) + .put("neunzehntel", 19L) + .put("zwanzigst", 20L) + .put("zwanzigster", 20L) + .put("zwanzigste", 20L) + .put("zwanzigstes", 20L) + .put("zwanzigsten", 20L) + .put("zwangtigstel", 20L) + .put("dreißigst", 30L) + .put("dreißigster", 30L) + .put("dreißigste", 30L) + .put("dreißigstes", 30L) + .put("dreißigsten", 30L) + .put("dreißigstel", 30L) + .put("vierzigst", 40L) + .put("vierzigster", 40L) + .put("vierzigste", 40L) + .put("vierzigstes", 40L) + .put("vierzigsten", 40L) + .put("vierzigstel", 40L) + .put("fünfzigst", 50L) + .put("fünfzigster", 50L) + .put("fünfzigste", 50L) + .put("fünfzigsten", 50L) + .put("fünfzigstes", 50L) + .put("fünfzigstel", 50L) + .put("fuenfzigst", 50L) + .put("fuenfzigster", 50L) + .put("fuenfzigste", 50L) + .put("fuenfzigstes", 50L) + .put("fuenfzigsten", 50L) + .put("fuenfzigstel", 50L) + .put("sechzigst", 60L) + .put("sechzigster", 60L) + .put("sechzigste", 60L) + .put("sechzigstes", 60L) + .put("sechzigsten", 60L) + .put("sechzigstel", 60L) + .put("siebzigst", 70L) + .put("siebzigster", 70L) + .put("siebzigste", 70L) + .put("siebzigstes", 70L) + .put("siebzigsten", 70L) + .put("siebzigstel", 70L) + .put("achtzigst", 80L) + .put("achtzigster", 80L) + .put("achtzigste", 80L) + .put("achtzigstes", 80L) + .put("achtzigsten", 80L) + .put("achtzigstel", 80L) + .put("neunzigst", 90L) + .put("neunzigster", 90L) + .put("neunzigste", 90L) + .put("neunzigstes", 90L) + .put("neunzigsten", 90L) + .put("neunzigstel", 90L) + .put("hundertst", 100L) + .put("hundertster", 100L) + .put("hundertste", 100L) + .put("hundertstes", 100L) + .put("hundertsten", 100L) + .put("hundertstel", 100L) + .put("tausendst", 1000L) + .put("tausendster", 1000L) + .put("tausendste", 1000L) + .put("tausendstes", 1000L) + .put("tausendsten", 1000L) + .put("tausendstel", 1000L) + .put("millionst", 1000000L) + .put("millionster", 1000000L) + .put("millionste", 1000000L) + .put("millionstes", 1000000L) + .put("millionsten", 1000000L) + .put("millionstel", 1000000L) + .put("milliardster", 1000000000L) + .put("milliardste", 1000000000L) + .put("milliardstes", 1000000000L) + .put("milliardsten", 1000000000L) + .put("milliardstel", 1000000000L) + .put("billionster", 1000000000000L) + .put("billionste", 1000000000000L) + .put("billionstes", 1000000000000L) + .put("billionsten", 1000000000000L) + .put("billionstel", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("hundert", 100L) + .put("tausend", 1000L) + .put("million", 1000000L) + .put("millionen", 1000000L) + .put("mio", 1000000L) + .put("milliard", 1000000000L) + .put("milliarde", 1000000000L) + .put("milliarden", 1000000000L) + .put("mrd", 1000000000L) + .put("billion", 1000000000000L) + .put("billionen", 1000000000000L) + .put("hundertstel", 100L) + .put("tausendstel", 1000L) + .put("millionstel", 1000000L) + .put("milliardstel", 1000000000L) + .put("billionstel", 1000000000000L) + .put("hundredths", 100L) + .put("dutzend", 12L) + .put("dutzende", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java new file mode 100644 index 000000000..3d0329efd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java @@ -0,0 +1,536 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class PortugueseNumeric { + + public static final String LangMarker = "Por"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = false; + + public static final String HundredsNumberIntegerRegex = "(quatrocent[ao]s|trezent[ao]s|seiscent[ao]s|setecent[ao]s|oitocent[ao]s|novecent[ao]s|duzent[ao]s|quinhent[ao]s|cem|(?({AllIntRegex})|((?({AllIntRegex})|((\\d+)(?!\\.)))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllFloatRegex = "{AllIntRegex}(\\s+(vírgula|virgula|e|ponto)){AllPointRegex}" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static final String DoubleWithMultiplierRegex = "(((? WrittenDecimalSeparatorTexts = Arrays.asList("virgula", "vírgula"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("ponto"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("e"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("com"); + + public static final List WrittenFractionSuffix = Arrays.asList("avo", "ava"); + + public static final Character PluralSuffix = 's'; + + public static final String HalfADozenRegex = "meia\\s+d[uú]zia"; + + public static final String DigitalNumberRegex = "((?<=\\b)(mil|cem|milh[oõ]es|milh[aã]o|bilh[oõ]es|bilh[aã]o|trilh[oõ]es|trilh[aã]o|milhares|centena|centenas|dezena|dezenas?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("zero", 0L) + .put("hum", 1L) + .put("um", 1L) + .put("uma", 1L) + .put("dois", 2L) + .put("duas", 2L) + .put("meia", 2L) + .put("meio", 2L) + .put("tres", 3L) + .put("três", 3L) + .put("quatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("sete", 7L) + .put("oito", 8L) + .put("nove", 9L) + .put("dez", 10L) + .put("dezena", 10L) + .put("déz", 10L) + .put("onze", 11L) + .put("doze", 12L) + .put("dúzia", 12L) + .put("duzia", 12L) + .put("dúzias", 12L) + .put("duzias", 12L) + .put("treze", 13L) + .put("catorze", 14L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("dezesseis", 16L) + .put("dezasseis", 16L) + .put("dezessete", 17L) + .put("dezassete", 17L) + .put("dezoito", 18L) + .put("dezenove", 19L) + .put("dezanove", 19L) + .put("vinte", 20L) + .put("trinta", 30L) + .put("quarenta", 40L) + .put("cinquenta", 50L) + .put("cincoenta", 50L) + .put("sessenta", 60L) + .put("setenta", 70L) + .put("oitenta", 80L) + .put("noventa", 90L) + .put("cem", 100L) + .put("cento", 100L) + .put("duzentos", 200L) + .put("duzentas", 200L) + .put("trezentos", 300L) + .put("trezentas", 300L) + .put("quatrocentos", 400L) + .put("quatrocentas", 400L) + .put("quinhentos", 500L) + .put("quinhentas", 500L) + .put("seiscentos", 600L) + .put("seiscentas", 600L) + .put("setecentos", 700L) + .put("setecentas", 700L) + .put("oitocentos", 800L) + .put("oitocentas", 800L) + .put("novecentos", 900L) + .put("novecentas", 900L) + .put("mil", 1000L) + .put("milhão", 1000000L) + .put("milhao", 1000000L) + .put("milhões", 1000000L) + .put("milhoes", 1000000L) + .put("bilhão", 1000000000L) + .put("bilhao", 1000000000L) + .put("bilhões", 1000000000L) + .put("bilhoes", 1000000000L) + .put("trilhão", 1000000000000L) + .put("trilhao", 1000000000000L) + .put("trilhões", 1000000000000L) + .put("trilhoes", 1000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("primeiro", 1L) + .put("primeira", 1L) + .put("segundo", 2L) + .put("segunda", 2L) + .put("terceiro", 3L) + .put("terceira", 3L) + .put("quarto", 4L) + .put("quarta", 4L) + .put("quinto", 5L) + .put("quinta", 5L) + .put("sexto", 6L) + .put("sexta", 6L) + .put("sétimo", 7L) + .put("setimo", 7L) + .put("sétima", 7L) + .put("setima", 7L) + .put("oitavo", 8L) + .put("oitava", 8L) + .put("nono", 9L) + .put("nona", 9L) + .put("décimo", 10L) + .put("decimo", 10L) + .put("décima", 10L) + .put("decima", 10L) + .put("undécimo", 11L) + .put("undecimo", 11L) + .put("undécima", 11L) + .put("undecima", 11L) + .put("duodécimo", 11L) + .put("duodecimo", 11L) + .put("duodécima", 11L) + .put("duodecima", 11L) + .put("vigésimo", 20L) + .put("vigesimo", 20L) + .put("vigésima", 20L) + .put("vigesima", 20L) + .put("trigésimo", 30L) + .put("trigesimo", 30L) + .put("trigésima", 30L) + .put("trigesima", 30L) + .put("quadragésimo", 40L) + .put("quadragesimo", 40L) + .put("quadragésima", 40L) + .put("quadragesima", 40L) + .put("quinquagésimo", 50L) + .put("quinquagesimo", 50L) + .put("quinquagésima", 50L) + .put("quinquagesima", 50L) + .put("sexagésimo", 60L) + .put("sexagesimo", 60L) + .put("sexagésima", 60L) + .put("sexagesima", 60L) + .put("septuagésimo", 70L) + .put("septuagesimo", 70L) + .put("septuagésima", 70L) + .put("septuagesima", 70L) + .put("setuagésimo", 70L) + .put("setuagesimo", 70L) + .put("setuagésima", 70L) + .put("setuagesima", 70L) + .put("octogésimo", 80L) + .put("octogesimo", 80L) + .put("octogésima", 80L) + .put("octogesima", 80L) + .put("nonagésimo", 90L) + .put("nonagesimo", 90L) + .put("nonagésima", 90L) + .put("nonagesima", 90L) + .put("centesimo", 100L) + .put("centésimo", 100L) + .put("centesima", 100L) + .put("centésima", 100L) + .put("ducentésimo", 200L) + .put("ducentesimo", 200L) + .put("ducentésima", 200L) + .put("ducentesima", 200L) + .put("tricentésimo", 300L) + .put("tricentesimo", 300L) + .put("tricentésima", 300L) + .put("tricentesima", 300L) + .put("trecentésimo", 300L) + .put("trecentesimo", 300L) + .put("trecentésima", 300L) + .put("trecentesima", 300L) + .put("quadringentésimo", 400L) + .put("quadringentesimo", 400L) + .put("quadringentésima", 400L) + .put("quadringentesima", 400L) + .put("quingentésimo", 500L) + .put("quingentesimo", 500L) + .put("quingentésima", 500L) + .put("quingentesima", 500L) + .put("sexcentésimo", 600L) + .put("sexcentesimo", 600L) + .put("sexcentésima", 600L) + .put("sexcentesima", 600L) + .put("seiscentésimo", 600L) + .put("seiscentesimo", 600L) + .put("seiscentésima", 600L) + .put("seiscentesima", 600L) + .put("septingentésimo", 700L) + .put("septingentesimo", 700L) + .put("septingentésima", 700L) + .put("septingentesima", 700L) + .put("setingentésimo", 700L) + .put("setingentesimo", 700L) + .put("setingentésima", 700L) + .put("setingentesima", 700L) + .put("octingentésimo", 800L) + .put("octingentesimo", 800L) + .put("octingentésima", 800L) + .put("octingentesima", 800L) + .put("noningentésimo", 900L) + .put("noningentesimo", 900L) + .put("noningentésima", 900L) + .put("noningentesima", 900L) + .put("nongentésimo", 900L) + .put("nongentesimo", 900L) + .put("nongentésima", 900L) + .put("nongentesima", 900L) + .put("milésimo", 1000L) + .put("milesimo", 1000L) + .put("milésima", 1000L) + .put("milesima", 1000L) + .put("milionésimo", 1000000L) + .put("milionesimo", 1000000L) + .put("milionésima", 1000000L) + .put("milionesima", 1000000L) + .put("bilionésimo", 1000000000L) + .put("bilionesimo", 1000000000L) + .put("bilionésima", 1000000000L) + .put("bilionesima", 1000000000L) + .build(); + + public static final ImmutableMap PrefixCardinalMap = ImmutableMap.builder() + .put("hum", 1L) + .put("um", 1L) + .put("dois", 2L) + .put("tres", 3L) + .put("três", 3L) + .put("quatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("sete", 7L) + .put("oito", 8L) + .put("nove", 9L) + .put("dez", 10L) + .put("onze", 11L) + .put("doze", 12L) + .put("treze", 13L) + .put("catorze", 14L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("dezesseis", 16L) + .put("dezasseis", 16L) + .put("dezessete", 17L) + .put("dezassete", 17L) + .put("dezoito", 18L) + .put("dezenove", 19L) + .put("dezanove", 19L) + .put("vinte", 20L) + .put("trinta", 30L) + .put("quarenta", 40L) + .put("cinquenta", 50L) + .put("cincoenta", 50L) + .put("sessenta", 60L) + .put("setenta", 70L) + .put("oitenta", 80L) + .put("noventa", 90L) + .put("cem", 100L) + .put("duzentos", 200L) + .put("trezentos", 300L) + .put("quatrocentos", 400L) + .put("quinhentos", 500L) + .put("seiscentos", 600L) + .put("setecentos", 700L) + .put("oitocentos", 800L) + .put("novecentos", 900L) + .build(); + + public static final ImmutableMap SuffixOrdinalMap = ImmutableMap.builder() + .put("milesimo", 1000L) + .put("milionesimo", 1000000L) + .put("bilionesimo", 1000000000L) + .put("trilionesimo", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("mil", 1000L) + .put("milesimo", 1000L) + .put("milhão", 1000000L) + .put("milhao", 1000000L) + .put("milhões", 1000000L) + .put("milhoes", 1000000L) + .put("milionésimo", 1000000L) + .put("milionesimo", 1000000L) + .put("bilhão", 1000000000L) + .put("bilhao", 1000000000L) + .put("bilhões", 1000000000L) + .put("bilhoes", 1000000000L) + .put("bilionésimo", 1000000000L) + .put("bilionesimo", 1000000000L) + .put("trilhão", 1000000000000L) + .put("trilhao", 1000000000000L) + .put("trilhões", 1000000000000L) + .put("trilhoes", 1000000000000L) + .put("trilionésimo", 1000000000000L) + .put("trilionesimo", 1000000000000L) + .put("dezena", 10L) + .put("dezenas", 10L) + .put("dúzia", 12L) + .put("duzia", 12L) + .put("dúzias", 12L) + .put("duzias", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java new file mode 100644 index 000000000..c4940e4a2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java @@ -0,0 +1,629 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class SpanishNumeric { + + public static final String LangMarker = "Spa"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = true; + + public static final String HundredsNumberIntegerRegex = "(cuatrocient[ao]s|trescient[ao]s|seiscient[ao]s|setecient[ao]s|ochocient[ao]s|novecient[ao]s|doscient[ao]s|quinient[ao]s|(?(?({AllIntRegex})|((?({AllIntRegex})|((\\d+)(?!\\.)))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s+{ZeroToNineIntegerRegex})+|(\\s+{AllIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AllIntRegex}", AllIntRegex); + + public static final String AllFloatRegex = "{AllIntRegex}(\\s+(coma|con)){AllPointRegex}" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static String DoubleDecimalPointRegex(String placeholder) { + return "(((?)"; + + public static final String LessRegex = "((meno(s|r(es)?)|inferior(es)?|por\\s+debajo)((\\s+(que|del?|al?)|(?=\\s+o\\b)))|más\\s+baj[oa]\\s+que|(?|=)<)"; + + public static final String EqualRegex = "((igual(es)?|equivalente(s)?|equivalen?)(\\s+(al?|que|del?))?|(?)=)"; + + public static final String MoreOrEqualPrefix = "((no\\s+{LessRegex})|(por\\s+lo\\s+menos|como\\s+m[íi]nimo|al\\s+menos))" + .replace("{LessRegex}", LessRegex); + + public static final String MoreOrEqual = "(({MoreRegex}\\s+(o)?\\s+{EqualRegex})|({EqualRegex}\\s+(o|y)\\s+{MoreRegex})|{MoreOrEqualPrefix}(\\s+(o)\\s+{EqualRegex})?|({EqualRegex}\\s+(o)\\s+)?{MoreOrEqualPrefix}|>\\s*=)" + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{MoreOrEqualPrefix}", MoreOrEqualPrefix); + + public static final String MoreOrEqualSuffix = "((\\b(y|o)\\b\\s+(m[áa]s|mayor(es)?|superior(es)?)((?!\\s+(alt[oa]|baj[oa]|que|del?|al?))|(\\s+(que|del?|al?)(?!(\\s*\\d+)))))|como\\s+m[íi]nimo|por\\s+lo\\s+menos|al\\s+menos)\\b"; + + public static final String LessOrEqualPrefix = "((no\\s+{MoreRegex})|(como\\s+(m[aá]ximo|mucho)))" + .replace("{MoreRegex}", MoreRegex); + + public static final String LessOrEqual = "(({LessRegex}\\s+(o)?\\s+{EqualRegex})|({EqualRegex}\\s+(o)?\\s+{LessRegex})|{LessOrEqualPrefix}(\\s+(o)?\\s+{EqualRegex})?|({EqualRegex}\\s+(o)?\\s+)?{LessOrEqualPrefix}|<\\s*=)" + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{LessOrEqualPrefix}", LessOrEqualPrefix); + + public static final String LessOrEqualSuffix = "((\\b(y|o)\\b\\s+(meno(s|r(es)?|inferior(es)?))((?!\\s+(alt[oa]|baj[oa]|que|del?|al?))|(\\s+(que|del?|al?)(?!(\\s*\\d+)))))|como\\s+m[áa]ximo)\\b"; + + public static final String NumberSplitMark = "(?![,.](?!\\d+))(?!\\s*\\b(((y|e)\\s+)?({LessRegex}|{MoreRegex}|{EqualRegex}|no|de)|pero|o|a)\\b)" + .replace("{LessRegex}", LessRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex); + + public static final String MoreRegexNoNumberSucceed = "(\\b(m[áa]s|mayor(es)?|superior(es)?)((?!\\s+(que|del?|al?))|\\s+((que|del?)(?!(\\s*\\d+))))|(por encima)(?!(\\s*\\d+)))\\b"; + + public static final String LessRegexNoNumberSucceed = "(\\b(meno(s|r(es)?)|inferior(es)?)((?!\\s+(que|del?|al?))|\\s+((que|del?|al?)(?!(\\s*\\d+))))|(por debajo)(?!(\\s*\\d+)))\\b"; + + public static final String EqualRegexNoNumberSucceed = "(\\b(igual(es)?|equivalentes?|equivalen?)((?!\\s+(al?|que|del?))|(\\s+(al?|que|del?)(?!(\\s*\\d+)))))\\b"; + + public static final String OneNumberRangeMoreRegex1 = "({MoreOrEqual}|{MoreRegex})\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{MoreOrEqual}", MoreOrEqual) + .replace("{MoreRegex}", MoreRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreRegex1LB = "(?({NumberSplitMark}.)+)\\s*{MoreOrEqualSuffix}" + .replace("{MoreOrEqualSuffix}", MoreOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){MoreRegexNoNumberSucceed})|({MoreRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{MoreRegexNoNumberSucceed}", MoreRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1 = "({LessOrEqual}|{LessRegex})\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{LessOrEqual}", LessOrEqual) + .replace("{LessRegex}", LessRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1LB = "(?({NumberSplitMark}.)+)\\s*{LessOrEqualSuffix}" + .replace("{LessOrEqualSuffix}", LessOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){LessRegexNoNumberSucceed})|({LessRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{LessRegexNoNumberSucceed}", LessRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeEqualRegex = "{EqualRegex}\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{EqualRegex}", EqualRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex1 = "\\bentre\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)\\s*y\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex2 = "({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})\\s*(\\by\\b|\\be\\b|pero|,)\\s*({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex3 = "({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})\\s*(\\by\\b|\\be\\b|pero|,)\\s*({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex4 = "(\\bde(sde)?\\s+)?(\\b(el|las?|los)\\s+)?\\b(?!\\s+)(?({NumberSplitMark}(?!\\b(entre|de(sde)?|es)\\b).)+)\\b\\s*{TillRegex}\\s*((el|las?|los)\\s+)?\\b(?!\\s+)(?({NumberSplitMark}.)+)\\b" + .replace("{TillRegex}", TillRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String AmbiguousFractionConnectorsRegex = "(\\b(en|de)\\b)"; + + public static final Character DecimalSeparatorChar = ','; + + public static final String FractionMarkerToken = "sobre"; + + public static final Character NonDecimalSeparatorChar = '.'; + + public static final String HalfADozenText = "seis"; + + public static final String WordSeparatorToken = "y"; + + public static final List WrittenDecimalSeparatorTexts = Arrays.asList("coma", "con"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("punto"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("y"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("con"); + + public static final String HalfADozenRegex = "media\\s+docena"; + + public static final String DigitalNumberRegex = "((?<=\\b)(mil(l[oó]n(es)?)?|bill[oó]n(es)?|trill[oó]n(es)?|docenas?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("cero", 0L) + .put("un", 1L) + .put("una", 1L) + .put("uno", 1L) + .put("dos", 2L) + .put("tres", 3L) + .put("cuatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("siete", 7L) + .put("ocho", 8L) + .put("nueve", 9L) + .put("diez", 10L) + .put("once", 11L) + .put("doce", 12L) + .put("docena", 12L) + .put("docenas", 12L) + .put("trece", 13L) + .put("catorce", 14L) + .put("quince", 15L) + .put("dieciseis", 16L) + .put("dieciséis", 16L) + .put("diecisiete", 17L) + .put("dieciocho", 18L) + .put("diecinueve", 19L) + .put("veinte", 20L) + .put("ventiuna", 21L) + .put("ventiuno", 21L) + .put("veintiun", 21L) + .put("veintiún", 21L) + .put("veintiuno", 21L) + .put("veintiuna", 21L) + .put("veintidos", 22L) + .put("veintidós", 22L) + .put("veintitres", 23L) + .put("veintitrés", 23L) + .put("veinticuatro", 24L) + .put("veinticinco", 25L) + .put("veintiseis", 26L) + .put("veintiséis", 26L) + .put("veintisiete", 27L) + .put("veintiocho", 28L) + .put("veintinueve", 29L) + .put("treinta", 30L) + .put("cuarenta", 40L) + .put("cincuenta", 50L) + .put("sesenta", 60L) + .put("setenta", 70L) + .put("ochenta", 80L) + .put("noventa", 90L) + .put("cien", 100L) + .put("ciento", 100L) + .put("doscientas", 200L) + .put("doscientos", 200L) + .put("trescientas", 300L) + .put("trescientos", 300L) + .put("cuatrocientas", 400L) + .put("cuatrocientos", 400L) + .put("quinientas", 500L) + .put("quinientos", 500L) + .put("seiscientas", 600L) + .put("seiscientos", 600L) + .put("setecientas", 700L) + .put("setecientos", 700L) + .put("ochocientas", 800L) + .put("ochocientos", 800L) + .put("novecientas", 900L) + .put("novecientos", 900L) + .put("mil", 1000L) + .put("millon", 1000000L) + .put("millón", 1000000L) + .put("millones", 1000000L) + .put("billon", 1000000000000L) + .put("billón", 1000000000000L) + .put("billones", 1000000000000L) + .put("trillon", 1000000000000000000L) + .put("trillón", 1000000000000000000L) + .put("trillones", 1000000000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("primero", 1L) + .put("primera", 1L) + .put("primer", 1L) + .put("segundo", 2L) + .put("segunda", 2L) + .put("medio", 2L) + .put("media", 2L) + .put("tercero", 3L) + .put("tercera", 3L) + .put("tercer", 3L) + .put("tercio", 3L) + .put("cuarto", 4L) + .put("cuarta", 4L) + .put("quinto", 5L) + .put("quinta", 5L) + .put("sexto", 6L) + .put("sexta", 6L) + .put("septimo", 7L) + .put("septima", 7L) + .put("séptimo", 7L) + .put("séptima", 7L) + .put("octavo", 8L) + .put("octava", 8L) + .put("noveno", 9L) + .put("novena", 9L) + .put("decimo", 10L) + .put("décimo", 10L) + .put("decima", 10L) + .put("décima", 10L) + .put("undecimo", 11L) + .put("undecima", 11L) + .put("undécimo", 11L) + .put("undécima", 11L) + .put("duodecimo", 12L) + .put("duodecima", 12L) + .put("duodécimo", 12L) + .put("duodécima", 12L) + .put("decimotercero", 13L) + .put("decimotercera", 13L) + .put("decimocuarto", 14L) + .put("decimocuarta", 14L) + .put("decimoquinto", 15L) + .put("decimoquinta", 15L) + .put("decimosexto", 16L) + .put("decimosexta", 16L) + .put("decimoseptimo", 17L) + .put("decimoseptima", 17L) + .put("decimoctavo", 18L) + .put("decimoctava", 18L) + .put("decimonoveno", 19L) + .put("decimonovena", 19L) + .put("vigesimo", 20L) + .put("vigesima", 20L) + .put("vigésimo", 20L) + .put("vigésima", 20L) + .put("trigesimo", 30L) + .put("trigesima", 30L) + .put("trigésimo", 30L) + .put("trigésima", 30L) + .put("cuadragesimo", 40L) + .put("cuadragesima", 40L) + .put("cuadragésimo", 40L) + .put("cuadragésima", 40L) + .put("quincuagesimo", 50L) + .put("quincuagesima", 50L) + .put("quincuagésimo", 50L) + .put("quincuagésima", 50L) + .put("sexagesimo", 60L) + .put("sexagesima", 60L) + .put("sexagésimo", 60L) + .put("sexagésima", 60L) + .put("septuagesimo", 70L) + .put("septuagesima", 70L) + .put("septuagésimo", 70L) + .put("septuagésima", 70L) + .put("octogesimo", 80L) + .put("octogesima", 80L) + .put("octogésimo", 80L) + .put("octogésima", 80L) + .put("nonagesimo", 90L) + .put("nonagesima", 90L) + .put("nonagésimo", 90L) + .put("nonagésima", 90L) + .put("centesimo", 100L) + .put("centesima", 100L) + .put("centésimo", 100L) + .put("centésima", 100L) + .put("ducentesimo", 200L) + .put("ducentesima", 200L) + .put("ducentésimo", 200L) + .put("ducentésima", 200L) + .put("tricentesimo", 300L) + .put("tricentesima", 300L) + .put("tricentésimo", 300L) + .put("tricentésima", 300L) + .put("cuadringentesimo", 400L) + .put("cuadringentesima", 400L) + .put("cuadringentésimo", 400L) + .put("cuadringentésima", 400L) + .put("quingentesimo", 500L) + .put("quingentesima", 500L) + .put("quingentésimo", 500L) + .put("quingentésima", 500L) + .put("sexcentesimo", 600L) + .put("sexcentesima", 600L) + .put("sexcentésimo", 600L) + .put("sexcentésima", 600L) + .put("septingentesimo", 700L) + .put("septingentesima", 700L) + .put("septingentésimo", 700L) + .put("septingentésima", 700L) + .put("octingentesimo", 800L) + .put("octingentesima", 800L) + .put("octingentésimo", 800L) + .put("octingentésima", 800L) + .put("noningentesimo", 900L) + .put("noningentesima", 900L) + .put("noningentésimo", 900L) + .put("noningentésima", 900L) + .put("milesimo", 1000L) + .put("milesima", 1000L) + .put("milésimo", 1000L) + .put("milésima", 1000L) + .put("millonesimo", 1000000L) + .put("millonesima", 1000000L) + .put("millonésimo", 1000000L) + .put("millonésima", 1000000L) + .put("billonesimo", 1000000000000L) + .put("billonesima", 1000000000000L) + .put("billonésimo", 1000000000000L) + .put("billonésima", 1000000000000L) + .build(); + + public static final ImmutableMap PrefixCardinalMap = ImmutableMap.builder() + .put("dos", 2L) + .put("tres", 3L) + .put("cuatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("siete", 7L) + .put("ocho", 8L) + .put("nueve", 9L) + .put("diez", 10L) + .put("once", 11L) + .put("doce", 12L) + .put("trece", 13L) + .put("catorce", 14L) + .put("quince", 15L) + .put("dieciseis", 16L) + .put("dieciséis", 16L) + .put("diecisiete", 17L) + .put("dieciocho", 18L) + .put("diecinueve", 19L) + .put("veinte", 20L) + .put("ventiuna", 21L) + .put("veintiun", 21L) + .put("veintiún", 21L) + .put("veintidos", 22L) + .put("veintitres", 23L) + .put("veinticuatro", 24L) + .put("veinticinco", 25L) + .put("veintiseis", 26L) + .put("veintisiete", 27L) + .put("veintiocho", 28L) + .put("veintinueve", 29L) + .put("treinta", 30L) + .put("cuarenta", 40L) + .put("cincuenta", 50L) + .put("sesenta", 60L) + .put("setenta", 70L) + .put("ochenta", 80L) + .put("noventa", 90L) + .put("cien", 100L) + .put("doscientos", 200L) + .put("trescientos", 300L) + .put("cuatrocientos", 400L) + .put("quinientos", 500L) + .put("seiscientos", 600L) + .put("setecientos", 700L) + .put("ochocientos", 800L) + .put("novecientos", 900L) + .build(); + + public static final ImmutableMap SuffixOrdinalMap = ImmutableMap.builder() + .put("milesimo", 1000L) + .put("millonesimo", 1000000L) + .put("billonesimo", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("mil", 1000L) + .put("milesimo", 1000L) + .put("millon", 1000000L) + .put("millón", 1000000L) + .put("millones", 1000000L) + .put("millonesimo", 1000000L) + .put("billon", 1000000000000L) + .put("billón", 1000000000000L) + .put("billones", 1000000000000L) + .put("billonesimo", 1000000000000L) + .put("trillon", 1000000000000000000L) + .put("trillón", 1000000000000000000L) + .put("trillones", 1000000000000000000L) + .put("trillonesimo", 1000000000000000000L) + .put("docena", 12L) + .put("docenas", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java new file mode 100644 index 000000000..661fc05f5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(SpanishNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + //Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + //Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java new file mode 100644 index 000000000..f088ab627 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(SpanishNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleSpa"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java new file mode 100644 index 000000000..0b319868c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracSpa"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracSpa"); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracSpa"); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java new file mode 100644 index 000000000..3ee5f10ad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java @@ -0,0 +1,64 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static IntegerExtractor getInstance() { + return getInstance(SpanishNumeric.PlaceHolderDefault); + } + + public static IntegerExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + IntegerExtractor instance = new IntegerExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + public IntegerExtractor() { + this(SpanishNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS) , "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumDot, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerSpa"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerSpa"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java new file mode 100644 index 000000000..1eb2385dc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java @@ -0,0 +1,104 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import static com.microsoft.recognizers.text.number.NumberMode.Default; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private NumberExtractor(NumberMode mode, NumberOptions options) { + HashMap builder = new HashMap<>(); + + //Add Cardinal + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(SpanishNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + //Add Fraction + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : SpanishNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..dd6003699 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java @@ -0,0 +1,52 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static OrdinalExtractor getInstance() { + return getInstance(""); + } + + private static OrdinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + OrdinalExtractor instance = new OrdinalExtractor(); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + public OrdinalExtractor() { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.OrdinalNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalSpa"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java new file mode 100644 index 000000000..ed3f5e5ab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(SpanishNumeric.NumberWithPrefixPercentage); + this.regexes = buildRegexes(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java new file mode 100644 index 000000000..38ced7f6b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java @@ -0,0 +1,129 @@ +package com.microsoft.recognizers.text.number.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class SpanishNumberParserConfiguration extends BaseNumberParserConfiguration { + + public SpanishNumberParserConfiguration() { + this(NumberOptions.None); + } + + public SpanishNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.Spanish), options); + } + + public SpanishNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + + super( + SpanishNumeric.LangMarker, + cultureInfo, + SpanishNumeric.CompoundNumberLanguage, + SpanishNumeric.MultiDecimalSeparatorCulture, + options, + SpanishNumeric.NonDecimalSeparatorChar, + SpanishNumeric.DecimalSeparatorChar, + SpanishNumeric.FractionMarkerToken, + SpanishNumeric.HalfADozenText, + SpanishNumeric.WordSeparatorToken, + SpanishNumeric.WrittenDecimalSeparatorTexts, + SpanishNumeric.WrittenGroupSeparatorTexts, + SpanishNumeric.WrittenIntegerSeparatorTexts, + SpanishNumeric.WrittenFractionSeparatorTexts, + SpanishNumeric.CardinalNumberMap, + buildOrdinalNumberMap(), + SpanishNumeric.RoundNumberMap, + + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.HalfADozenRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DigitalNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NegativeNumberSignRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS)); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + + List result = new ArrayList<>(); + + for (String token : tokens) { + String tempWord = QueryProcessor.trimEnd(token, "s"); + if (this.getOrdinalNumberMap().containsKey(tempWord)) { + result.add(tempWord); + continue; + } + + if (tempWord.endsWith("avo") || tempWord.endsWith("ava")) { + String origTempWord = tempWord; + int newLength = origTempWord.length(); + tempWord = origTempWord.substring(0, newLength - 3); + if (this.getCardinalNumberMap().containsKey(tempWord)) { + result.add(tempWord); + continue; + } else { + tempWord = origTempWord.substring(0, newLength - 2); + if (this.getCardinalNumberMap().containsKey(tempWord)) { + result.add(tempWord); + continue; + } + } + } + + result.add(token); + } + + return result; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + if (this.getOrdinalNumberMap().containsKey(numberStr)) { + return this.getOrdinalNumberMap().get(numberStr); + } + + if (this.getCardinalNumberMap().containsKey(numberStr)) { + return this.getCardinalNumberMap().get(numberStr); + } + + long value = 0; + long finalValue = 0; + StringBuilder strBuilder = new StringBuilder(); + int lastGoodChar = 0; + for (int i = 0; i < numberStr.length(); i++) { + strBuilder.append(numberStr.charAt(i)); + if (this.getCardinalNumberMap().containsKey(strBuilder.toString()) && this.getCardinalNumberMap().get(strBuilder.toString()) > value) { + lastGoodChar = i; + value = this.getCardinalNumberMap().get(strBuilder.toString()); + } + if ((i + 1) == numberStr.length()) { + finalValue += value; + strBuilder = new StringBuilder(); + i = lastGoodChar++; + value = 0; + } + } + return finalValue; + } + + private static Map buildOrdinalNumberMap() { + ImmutableMap.Builder ordinalNumberMapBuilder = new ImmutableMap.Builder() + .putAll(SpanishNumeric.OrdinalNumberMap); + SpanishNumeric.SuffixOrdinalMap.forEach((sufixKey, sufixValue) -> + SpanishNumeric.PrefixCardinalMap.forEach((prefixKey, prefixValue) -> + ordinalNumberMapBuilder.put(prefixKey + sufixKey, prefixValue * sufixValue))); + + return ordinalNumberMapBuilder.build(); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java new file mode 100644 index 000000000..1ebe407e2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit; + +public class Constants { + public static final String SYS_UNIT_DIMENSION = "builtin.unit.dimension"; + public static final String SYS_UNIT = "builtin.unit"; + public static final String SYS_UNIT_AGE = "builtin.unit.age"; + public static final String SYS_UNIT_AREA = "builtin.unit.area"; + public static final String SYS_UNIT_CURRENCY = "builtin.unit.currency"; + public static final String SYS_UNIT_LENGTH = "builtin.unit.length"; + public static final String SYS_UNIT_SPEED = "builtin.unit.speed"; + public static final String SYS_UNIT_TEMPERATURE = "builtin.unit.temperature"; + public static final String SYS_UNIT_VOLUME = "builtin.unit.volume"; + public static final String SYS_UNIT_WEIGHT = "builtin.unit.weight"; + public static final String SYS_NUM = "builtin.num"; + + // For currencies without ISO codes, we use internal values prefixed by '_'. + // These values should never be present in parse output. + public static final String FAKE_ISO_CODE_PREFIX = "_"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java new file mode 100644 index 000000000..5cb560399 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.numberwithunit; + +public enum NumberWithUnitOptions { + None(0); + + private final int value; + + NumberWithUnitOptions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java new file mode 100644 index 000000000..44d5caf93 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java @@ -0,0 +1,256 @@ +package com.microsoft.recognizers.text.numberwithunit; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.numberwithunit.extractors.BaseMergedUnitExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.NumberWithUnitExtractor; +import com.microsoft.recognizers.text.numberwithunit.models.AgeModel; +import com.microsoft.recognizers.text.numberwithunit.models.CurrencyModel; +import com.microsoft.recognizers.text.numberwithunit.models.DimensionModel; +import com.microsoft.recognizers.text.numberwithunit.models.TemperatureModel; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseMergedUnitParser; +import com.microsoft.recognizers.text.numberwithunit.parsers.NumberWithUnitParser; + +import java.util.List; +import java.util.function.Function; + +public class NumberWithUnitRecognizer extends Recognizer { + + public NumberWithUnitRecognizer() { + this(null, NumberWithUnitOptions.None, true); + } + + public NumberWithUnitRecognizer(String culture) { + this(culture, NumberWithUnitOptions.None, false); + } + + public NumberWithUnitRecognizer(NumberWithUnitOptions options) { + this(null, options, true); + } + + public NumberWithUnitRecognizer(NumberWithUnitOptions options, boolean lazyInitialization) { + this(null, options, lazyInitialization); + } + + public NumberWithUnitRecognizer(String culture, NumberWithUnitOptions options, boolean lazyInitialization) { + super(culture, options, lazyInitialization); + } + + public CurrencyModel getCurrencyModel() { + return getCurrencyModel(null, true); + } + + public CurrencyModel getCurrencyModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(CurrencyModel.class, culture, fallbackToDefaultCulture); + } + + public TemperatureModel getTemperatureModel() { + return getTemperatureModel(null, true); + } + + public TemperatureModel getTemperatureModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(TemperatureModel.class, culture, fallbackToDefaultCulture); + } + + public AgeModel getAgeModel() { + return getAgeModel(null, true); + } + + public AgeModel getAgeModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(AgeModel.class, culture, fallbackToDefaultCulture); + } + + public DimensionModel getDimensionModel() { + return getDimensionModel(null, true); + } + + public DimensionModel getDimensionModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(DimensionModel.class, culture, fallbackToDefaultCulture); + } + + //region Helper methods for less verbosity + public static List recognizeCurrency(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getCurrencyModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeCurrency(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getCurrencyModel(culture, true), query, options); + } + + public static List recognizeCurrency(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getCurrencyModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeTemperature(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getTemperatureModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeTemperature(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getTemperatureModel(culture, true), query, options); + } + + public static List recognizeTemperature(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getTemperatureModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeAge(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getAgeModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeAge(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getAgeModel(culture, true), query, options); + } + + public static List recognizeAge(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getAgeModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeDimension(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getDimensionModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeDimension(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getDimensionModel(culture, true), query, options); + } + + public static List recognizeDimension(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getDimensionModel(culture, fallbackToDefaultCulture), query, options); + } + //endregion + + private static List recognizeByModel(Function getModelFun, String query, NumberWithUnitOptions options) { + NumberWithUnitRecognizer recognizer = new NumberWithUnitRecognizer(options); + IModel model = getModelFun.apply(recognizer); + return model.parse(query); + } + + @Override + protected void initializeConfiguration() { + + //region English + registerModel(CurrencyModel.class, Culture.English, (options) -> + new CurrencyModel(ImmutableMap.of( + new BaseMergedUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.CurrencyExtractorConfiguration()), + new BaseMergedUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.English, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.English, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.English, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.AgeParserConfiguration())))); + //endregion + + //region Spanish + registerModel(CurrencyModel.class, Culture.Spanish, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.Spanish, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.Spanish, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.Spanish, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.AgeParserConfiguration())))); + //endregion + + //region Portuguese + registerModel(CurrencyModel.class, Culture.Portuguese, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.Portuguese, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.Portuguese, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.Portuguese, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.AgeParserConfiguration())))); + //endregion + + //region French + registerModel(CurrencyModel.class, Culture.French, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.French, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.French, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.French, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.AgeParserConfiguration())))); + //endregion + + //region German + registerModel(CurrencyModel.class, Culture.German, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.German, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.German, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.German, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.AgeParserConfiguration())))); + //endregion + + + //region Chinese + registerModel(CurrencyModel.class, Culture.Chinese, (options) -> + new CurrencyModel(ImmutableMap.of( + new BaseMergedUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.CurrencyExtractorConfiguration()), + new BaseMergedUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.CurrencyParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.Chinese, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.TemperatureParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.Chinese, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.DimensionParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.Chinese, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.AgeParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.AgeParserConfiguration())))); + //endregion + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..722dc26f7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.AgeAmbiguousValues; + } + + public static Map AgeSuffixList = ChineseNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..fa27f0291 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,112 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class ChineseNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + private final Pattern halfUnitRegex = Pattern.compile(ChineseNumericWithUnit.HalfUnitRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected ChineseNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = new NumberExtractor(ChineseNumberExtractorMode.ExtractAll); + this.compoundUnitConnectorRegex = + Pattern.compile(ChineseNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(ChineseNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return ChineseNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return ChineseNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return ChineseNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public Pattern getHalfUnitRegex() { + return Pattern.compile(ChineseNumericWithUnit.HalfUnitRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + // Expand Chinese phrase to the `half` patterns when it follows closely origin phrase. + if (halfUnitRegex != null) { + Match[] match = RegExpUtility.getMatches(halfUnitRegex, source); + if (match.length > 0) { + List res = new ArrayList<>(); + for (ExtractResult er : result) { + int start = er.getStart(); + int length = er.getLength(); + List matchSuffix = new ArrayList<>(); + for (Match mr : match) { + if (mr.index == (start + length)) { + ExtractResult m = new ExtractResult(mr.index, mr.length, mr.value, numbers.get(0).getType(), numbers.get(0).getData()); + matchSuffix.add(m); + } + } + if (matchSuffix.size() == 1) { + ExtractResult mr = matchSuffix.get(0); + er.setStart(er.getLength() + mr.getLength()); + er.setText(er.getText() + mr.getText()); + List tmp = new ArrayList<>(); + tmp.add((ExtractResult)er.getData()); + tmp.add(mr); + er.setData(tmp); + } + res.add(er); + } + result = res; + } + } + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..2c6263440 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.CurrencyAmbiguousValues; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = ChineseNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = ChineseNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..af30286c3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.DimensionAmbiguousValues; + } + + public static Map DimensionSuffixList = ChineseNumericWithUnit.DimensionSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..b5e265aad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,58 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return TemperaturePrefixList; + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.TemperatureAmbiguousValues; + } + + public static Map TemperatureSuffixList = ChineseNumericWithUnit.TemperatureSuffixList; + + public static Map TemperaturePrefixList = ChineseNumericWithUnit.TemperaturePrefixList; + + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..1d6088fc5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..01783b66c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; + +public class ChineseNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IExtractor internalNumberExtractor; + private final IParser internalNumberParser; + private final String connectorToken; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return this.connectorToken; + } + + public ChineseNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = new NumberExtractor(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new ChineseNumberParserConfiguration()); + this.connectorToken = ""; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..3d2bf08e7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,32 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.CurrencyExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.Map; + +public class CurrencyParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + @Override + public Map getCurrencyNameToIsoCodeMap() { + return ChineseNumericWithUnit.CurrencyNameToIsoCodeMap; + } + + @Override + public Map getCurrencyFractionCodeList() { + return ChineseNumericWithUnit.FractionalUnitNameToCodeMap; + } + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..0e9011fa4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..1e9a0ed62 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..b48a2d12c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,45 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return AgePrefixList; + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = EnglishNumericWithUnit.AgeSuffixList; + + public static Map AgePrefixList = EnglishNumericWithUnit.AgePrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..255969e3e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = EnglishNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..f4b00fa87 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = EnglishNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = EnglishNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..bd13a1589 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(EnglishNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..de4892583 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.english.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class EnglishNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected EnglishNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit); + this.compoundUnitConnectorRegex = + Pattern.compile(EnglishNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(EnglishNumericWithUnit.AmbiguityFiltersDict); + + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return EnglishNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return EnglishNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return ""; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..cd1c8fd7d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = EnglishNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..3045d7042 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = EnglishNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..ae04773d5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousTemperatureUnitList; + } + + public static Map TemperatureSuffixList = new HashMap(EnglishNumericWithUnit.TemperatureSuffixList); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..5e6c50543 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousVolumeUnitList; + } + + public static Map VolumeSuffixList = EnglishNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..37d96067a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousWeightUnitList; + } + + public static Map WeightSuffixList = EnglishNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..83fd17503 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + this.bindDictionary(AgeExtractorConfiguration.AgePrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..1ab718900 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..4448e9f5f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,32 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.CurrencyExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Map; + +public class CurrencyParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + @Override + public Map getCurrencyNameToIsoCodeMap() { + return EnglishNumericWithUnit.CurrencyNameToIsoCodeMap; + } + + @Override + public Map getCurrencyFractionCodeList() { + return EnglishNumericWithUnit.FractionalUnitNameToCodeMap; + } + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..e0df165af --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..9b915d91d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.english.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; + +public abstract class EnglishNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return ""; + } + + public EnglishNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new EnglishNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..dc93b9db7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..567c1deba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..ed4ee83c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..6515fecbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..605661faa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java new file mode 100644 index 000000000..07882ddb4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java @@ -0,0 +1,185 @@ +package com.microsoft.recognizers.text.numberwithunit.extractors; + +import com.google.common.collect.Lists; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.numberwithunit.Constants; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +public class BaseMergedUnitExtractor implements IExtractor { + + private final INumberWithUnitExtractorConfiguration config; + + public BaseMergedUnitExtractor(INumberWithUnitExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String source) { + // Only merge currency's compound units for now. + if (config.getExtractType().equals(Constants.SYS_UNIT_CURRENCY)) { + return mergeCompoundUnits(source); + } else { + return new NumberWithUnitExtractor(config).extract(source); + } + } + + @SuppressWarnings("unchecked") + private List mergeCompoundUnits(String source) { + List ers = new NumberWithUnitExtractor(config).extract(source); + mergePureNumber(source, ers); + + if (ers.size() == 0) { + return ers; + } + + List result = new ArrayList<>(); + int[] groups = new int[ers.size()]; + groups[0] = 0; + for (int idx = 0; idx < ers.size() - 1; idx++) { + if (!ers.get(idx).getType().equals(ers.get(idx + 1).getType()) && + !ers.get(idx).getType().equals(Constants.SYS_NUM) && + !ers.get(idx + 1).getType().equals(Constants.SYS_NUM)) { + continue; + } + + if (ers.get(idx).getData() instanceof ExtractResult && !((ExtractResult)ers.get(idx).getData()).getData().toString().startsWith("Integer")) { + groups[idx + 1] = groups[idx] + 1; + continue; + } + + int middleBegin = ers.get(idx).getStart() + ers.get(idx).getLength(); + int middleEnd = ers.get(idx + 1).getStart(); + + String middleStr = source.substring(middleBegin, middleEnd).trim().toLowerCase(); + + // Separated by whitespace + if (middleStr.isEmpty()) { + groups[idx + 1] = groups[idx]; + continue; + } + + // Separated by connectors + Matcher match = config.getCompoundUnitConnectorRegex().matcher(middleStr); + if (match.find() && match.start() == 0 && (match.end() - match.start()) == middleStr.length()) { + groups[idx + 1] = groups[idx]; + } else { + groups[idx + 1] = groups[idx] + 1; + } + } + + for (int idx = 0; idx < ers.size(); idx++) { + if (idx == 0 || groups[idx] != groups[idx - 1]) { + ExtractResult tmpExtractResult = ers.get(idx); + tmpExtractResult.setData(Lists.newArrayList( + new ExtractResult( + tmpExtractResult.getStart(), + tmpExtractResult.getLength(), + tmpExtractResult.getText(), + tmpExtractResult.getType(), + tmpExtractResult.getData()))); + + ers.set(idx, tmpExtractResult); + result.add(tmpExtractResult); + } + + // Reduce extract results in same group + if (idx + 1 < ers.size() && groups[idx + 1] == groups[idx]) { + int group = groups[idx]; + + int periodBegin = result.get(group).getStart(); + int periodEnd = ers.get(idx + 1).getStart() + ers.get(idx + 1).getLength(); + + ExtractResult r = result.get(group); + + List data = (List)r.getData(); + data.add(ers.get(idx + 1)); + r.setLength(periodEnd - periodBegin); + r.setText(source.substring(periodBegin, periodEnd)); + r.setType(Constants.SYS_UNIT_CURRENCY); + r.setData(data); + + result.set(group, r); + } + } + + for (int idx = 0; idx < result.size(); idx++) { + if (result.get(idx).getData() instanceof List) { + List innerData = (List)result.get(idx).getData(); + if (innerData.size() == 1) { + result.set(idx, innerData.get(0)); + } + } + } + + result = result.stream().filter(o -> !o.getType().equals(Constants.SYS_NUM)) + .collect(Collectors.toList()); + + return result; + } + + private void mergePureNumber(String source, List ers) { + + List numErs = config.getUnitNumExtractor().extract(source); + List unitNumbers = new ArrayList<>(); + for (int i = 0, j = 0; i < numErs.size(); i++) { + boolean hasBehindExtraction = false; + while (j < ers.size() && ers.get(j).getStart() + ers.get(j).getLength() < numErs.get(i).getStart()) { + hasBehindExtraction = true; + j++; + } + + if (!hasBehindExtraction) { + continue; + } + + int middleBegin = ers.get(j - 1).getStart() + ers.get(j - 1).getLength(); + int middleEnd = numErs.get(i).getStart(); + + String middleStr = source.substring(middleBegin, middleEnd).trim().toLowerCase(); + + // Separated by whitespace + if (middleStr.isEmpty()) { + unitNumbers.add(numErs.get(i)); + continue; + } + + // Separated by connectors + Matcher match = config.getCompoundUnitConnectorRegex().matcher(middleStr); + if (match.find()) { + int start = match.start(); + int end = match.end(); + int length = end - start; + + if (start == 0 && length == middleStr.length()) { + unitNumbers.add(numErs.get(i)); + } + } + } + + for (ExtractResult extractResult : unitNumbers) { + boolean overlap = false; + for (ExtractResult er : ers) { + if (er.getStart() <= extractResult.getStart() && er.getStart() + er.getLength() >= extractResult.getStart()) { + overlap = true; + } + } + + if (!overlap) { + ers.add(extractResult); + } + } + + Collections.sort(ers, (Comparator)(xo, yo) -> { + ExtractResult x = (ExtractResult)xo; + ExtractResult y = (ExtractResult)yo; + return x.getStart() - y.getStart(); + }); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..45d80dd7d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.numberwithunit.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface INumberWithUnitExtractorConfiguration { + + Map getSuffixList(); + + Map getPrefixList(); + + List getAmbiguousUnitList(); + + String getExtractType(); + + CultureInfo getCultureInfo(); + + IExtractor getUnitNumExtractor(); + + String getBuildPrefix(); + + String getBuildSuffix(); + + String getConnectorToken(); + + Pattern getCompoundUnitConnectorRegex(); + + Pattern getAmbiguousUnitNumberMultiplierRegex(); + + Map getAmbiguityFiltersDict(); + + List expandHalfSuffix(String source, List result, List numbers); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java new file mode 100644 index 000000000..e3a9e3b62 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java @@ -0,0 +1,427 @@ +package com.microsoft.recognizers.text.numberwithunit.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.models.PrefixUnitResult; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.utilities.StringComparer; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class NumberWithUnitExtractor implements IExtractor { + + private final INumberWithUnitExtractorConfiguration config; + + private final Set suffixRegexes; + private final Set prefixRegexes; + + private final Pattern separateRegex; + private final Pattern singleCharUnitRegex = Pattern.compile(BaseUnits.SingleCharUnitRegex, Pattern.UNICODE_CHARACTER_CLASS); + + private final int maxPrefixMatchLen; + + private final List separators = Arrays.asList("|"); + + public NumberWithUnitExtractor(INumberWithUnitExtractorConfiguration config) { + this.config = config; + + if (config.getSuffixList() != null && config.getSuffixList().size() > 0) { + suffixRegexes = buildRegexFromSet(this.config.getSuffixList().values()); + } else { + suffixRegexes = new HashSet<>(); + } + + int tempMaxPrefixMatchLen = 0; + if (this.config.getPrefixList() != null && this.config.getPrefixList().size() > 0) { + for (String preMatch : this.config.getPrefixList().values()) { + List matchList = QueryProcessor.split(preMatch, separators); + for (String match : matchList) { + tempMaxPrefixMatchLen = tempMaxPrefixMatchLen >= match.length() ? tempMaxPrefixMatchLen : match.length(); + } + } + + // 2 is the maximum length of spaces. + tempMaxPrefixMatchLen += 2; + maxPrefixMatchLen = tempMaxPrefixMatchLen; + prefixRegexes = buildRegexFromSet(this.config.getPrefixList().values()); + } else { + maxPrefixMatchLen = 0; + prefixRegexes = new HashSet<>(); + } + + separateRegex = buildSeparateRegexFromSet(); + } + + @Override + public List extract(String source) { + List result = new ArrayList<>(); + + if (!preCheckStr(source)) { + return result; + } + + Map mappingPrefix = new HashMap(); + boolean[] matched = new boolean[source.length()]; + Arrays.fill(matched, false); + List numbers = this.config.getUnitNumExtractor().extract(source); + int sourceLen = source.length(); + + List prefixMatch = new ArrayList(); + List suffixMatch = new ArrayList(); + + for (Pattern regex : prefixRegexes) { + Matcher match = regex.matcher(source); + if (match.find()) { + prefixMatch.add(match); + } + } + + for (Pattern regex : suffixRegexes) { + Matcher match = regex.matcher(source); + if (match.find()) { + suffixMatch.add(match); + } + } + + if (numbers.size() > 0 && this.config.getExtractType() == Constants.SYS_UNIT_CURRENCY && prefixMatch.size() > 0 && suffixMatch.size() > 0) { + + for (ExtractResult number : numbers) { + int start = number.getStart(); + int length = number.getLength(); + Boolean numberPrefix = false; + Boolean numberSuffix = false; + + for (Matcher match : prefixMatch) { + if (match.end() == start) { + numberPrefix = true; + } + } + + for (Matcher match : suffixMatch) { + if (start + length == match.start()) { + numberSuffix = true; + } + } + + if (numberPrefix && numberSuffix && number.getText().contains(",")) { + int commaIndex = start + number.getText().indexOf(","); + source = source.substring(0, commaIndex) + " " + source.substring(commaIndex + 1); + } + } + numbers = this.config.getUnitNumExtractor().extract(source); + } + + /* Special case for cases where number multipliers clash with unit */ + Pattern ambiguousMultiplierRegex = this.config.getAmbiguousUnitNumberMultiplierRegex(); + if (ambiguousMultiplierRegex != null) { + for (int i = 0; i < numbers.size(); i++) { + ExtractResult number = numbers.get(i); + + Match[] matches = RegExpUtility.getMatches(ambiguousMultiplierRegex, number.getText()); + if (matches.length == 1) { + int newLength = number.getLength() - matches[0].length; + numbers.set(i, new ExtractResult(number.getStart(), newLength, number.getText().substring(0, newLength), + number.getType(), number.getData())); + } + } + } + + /* Mix prefix and numbers, make up a prefix-number combination */ + if (maxPrefixMatchLen != 0) { + for (ExtractResult number : numbers) { + if (number.getStart() == null || number.getLength() == null) { + continue; + } + + int maxFindPref = Math.min(maxPrefixMatchLen, number.getStart()); + if (maxFindPref == 0) { + continue; + } + + /* Scan from left to right , find the longest match */ + String leftStr = source.substring(number.getStart() - maxFindPref, number.getStart()); + int lastIndex = leftStr.length(); + + MatchResult bestMatch = null; + for (Pattern regex : prefixRegexes) { + Matcher match = regex.matcher(leftStr); + while (match.find()) { + if (leftStr.substring(match.start(), lastIndex).trim().equals(match.group())) { + if (bestMatch == null || bestMatch.start() >= match.start()) { + bestMatch = match.toMatchResult(); + } + } + } + } + + if (bestMatch != null) { + int offset = lastIndex - bestMatch.start(); + String unitStr = leftStr.substring(bestMatch.start(), lastIndex); + mappingPrefix.put(number.getStart(), new PrefixUnitResult(offset, unitStr)); + } + } + } + + for (ExtractResult number : numbers) { + if (number.getStart() == null || number.getLength() == null) { + continue; + } + + int start = number.getStart(); + int length = number.getLength(); + int maxFindLen = sourceLen - start - length; + + PrefixUnitResult prefixUnit = null; + if (mappingPrefix.containsKey(start)) { + prefixUnit = mappingPrefix.get(start); + } + + if (maxFindLen > 0) { + String rightSub = source.substring(start + length, start + length + maxFindLen); + List unitMatch = suffixRegexes.stream().map(p -> p.matcher(rightSub)).collect(Collectors.toList()); + + int maxlen = 0; + for (int i = 0; i < unitMatch.size(); i++) { + Matcher m = unitMatch.get(i); + while (m.find()) { + int endpos = m.end(); + if (m.start() >= 0) { + String midStr = rightSub.substring(0, Math.min(m.start(), rightSub.length())); + if (maxlen < endpos && (midStr.trim().isEmpty() || midStr.trim().equalsIgnoreCase(this.config.getConnectorToken()))) { + maxlen = endpos; + } + } + } + } + + if (maxlen != 0) { + for (int i = 0; i < length + maxlen; i++) { + matched[i + start] = true; + } + + String substr = source.substring(start, start + length + maxlen); + ExtractResult er = new ExtractResult(start, length + maxlen, substr, this.config.getExtractType(), null); + + if (prefixUnit != null) { + er.setStart(er.getStart() - prefixUnit.offset); + er.setLength(er.getLength() + prefixUnit.offset); + er.setText(prefixUnit.unitStr + er.getText()); + } + + /* Relative position will be used in Parser */ + number.setStart(start - er.getStart()); + er.setData(number); + result.add(er); + + continue; + } + } + + if (prefixUnit != null) { + ExtractResult er = new ExtractResult( + number.getStart() - prefixUnit.offset, + number.getLength() + prefixUnit.offset, + prefixUnit.unitStr + number.getText(), + this.config.getExtractType(), + null); + + /* Relative position will be used in Parser */ + number.setStart(start - er.getStart()); + er.setData(number); + result.add(er); + } + } + + // Extract Separate unit + if (separateRegex != null) { + extractSeparateUnits(source, result); + } + + // Remove common ambiguous cases + result = filterAmbiguity(result, source); + + // Expand Chinese phrase to the `half` patterns when it follows closely origin phrase. + result = this.config.expandHalfSuffix(source, result, numbers); + + return result; + } + + private List filterAmbiguity(List extractResults, String input) { + + if (this.config.getAmbiguityFiltersDict() != null) { + + for (Map.Entry pair : this.config.getAmbiguityFiltersDict().entrySet()) { + + final Pattern key = pair.getKey(); + final Pattern value = pair.getValue(); + + for (ExtractResult extractResult : extractResults) { + Optional keyMatch = Arrays.stream(RegExpUtility.getMatches(key, extractResult.getText())).findFirst(); + if (keyMatch.isPresent()) { + final Match[] matches = RegExpUtility.getMatches(value, input); + extractResults = extractResults.stream() + .filter(er -> Arrays.stream(matches).noneMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) + .collect(Collectors.toCollection(ArrayList::new)); + } + } + } + } + + // Filter single-char units if not exact match + extractResults = extractResults.stream().filter(er -> !(er.getLength() != input.length() && Pattern.matches(singleCharUnitRegex.toString(), er.getText()))) + .collect(Collectors.toCollection(ArrayList::new)); + + return extractResults; + } + + public void extractSeparateUnits(String source, List numDependResults) { + //Default is false + boolean[] matchResult = new boolean[source.length()]; + Arrays.fill(matchResult, false); + + for (ExtractResult numDependResult : numDependResults) { + int start = numDependResult.getStart(); + int i = 0; + do { + matchResult[start + i++] = true; + } while (i < numDependResult.getLength()); + } + + //Extract all SeparateUnits, then merge it with numDependResults + Matcher matcher = separateRegex.matcher(source); + while (matcher.find()) { + + + int start = matcher.start(); + int end = matcher.end(); + int length = end - start; + + int i = 0; + while (i < length && !matchResult[start + i]) { + i++; + } + + if (i == length) { + //Mark as extracted + for (int j = 0; j < i; j++) { + matchResult[j] = true; + } + + numDependResults.add(new ExtractResult( + start, + length, + matcher.group(), + this.config.getExtractType(), + null)); + } + } + } + + protected boolean preCheckStr(String str) { + return str != null && !str.isEmpty(); + } + + protected Set buildRegexFromSet(Collection values) { + return buildRegexFromSet(values, false); + } + + protected Set buildRegexFromSet(Collection collection, boolean ignoreCase) { + + Set regexes = new HashSet<>(); + for (String regexString : collection) { + List regexTokens = new ArrayList<>(); + for (String token : QueryProcessor.split(regexString, Arrays.asList("|"))) { + regexTokens.add(Pattern.quote(token)); + } + + String pattern = String.format( + "%s(%s)%s", + this.config.getBuildPrefix(), + String.join("|", regexTokens), + this.config.getBuildSuffix()); + + int options = Pattern.UNICODE_CHARACTER_CLASS | (ignoreCase ? Pattern.CASE_INSENSITIVE : 0); + + Pattern regex = Pattern.compile(pattern, options); + regexes.add(regex); + } + + return regexes; + } + + protected Pattern buildSeparateRegexFromSet() { + return buildSeparateRegexFromSet(false); + } + + protected Pattern buildSeparateRegexFromSet(boolean ignoreCase) { + + Set separateWords = new HashSet<>(); + if (config.getPrefixList() != null && config.getPrefixList().size() > 0) { + for (String addWord : config.getPrefixList().values()) { + + for (String word : QueryProcessor.split(addWord, separators)) { + if (validateUnit(word)) { + separateWords.add(word); + } + } + } + } + + if (config.getSuffixList() != null && config.getSuffixList().size() > 0) { + for (String addWord : config.getSuffixList().values()) { + for (String word : QueryProcessor.split(addWord, separators)) { + if (validateUnit(word)) { + separateWords.add(word); + } + } + } + } + + if (config.getAmbiguousUnitList() != null && config.getAmbiguousUnitList().size() > 0) { + List abandonWords = config.getAmbiguousUnitList(); + for (String abandonWord : abandonWords) { + if (separateWords.contains(abandonWord)) { + separateWords.remove(abandonWord); + } + } + } + + //Sort separateWords using descending length. + List regexTokens = separateWords.stream().map(s -> Pattern.quote(s)).collect(Collectors.toList()); + if (regexTokens.size() == 0) { + return null; + } + + Collections.sort(regexTokens, new StringComparer()); + String pattern = String.format( + "%s(%s)%s", + this.config.getBuildPrefix(), + String.join("|", regexTokens), + this.config.getBuildSuffix()); + int options = Pattern.UNICODE_CHARACTER_CLASS | (ignoreCase ? Pattern.CASE_INSENSITIVE : 0); + + Pattern regex = Pattern.compile(pattern, options); + return regex; + } + + public boolean validateUnit(String source) { + return !source.startsWith("-"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..1dcf822d6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AgeSuffixList = FrenchNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..eb86278aa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = FrenchNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..0f3fa303b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = FrenchNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = FrenchNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..d88234dab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(FrenchNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..6f7a5b683 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.french.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class FrenchNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected FrenchNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit); + this.compoundUnitConnectorRegex = + Pattern.compile(FrenchNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(FrenchNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return FrenchNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return FrenchNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return FrenchNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..13e4a6cb1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = FrenchNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..6ac2915c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = FrenchNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..a30c3b9be --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map TemperatureSuffixList = new HashMap(FrenchNumericWithUnit.TemperatureSuffixList); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..8adcf5b53 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousVolumeUnitList; + } + + public static Map VolumeSuffixList = FrenchNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..78b36f3f3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map WeightSuffixList = FrenchNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..333b88e7b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..ae7faf78a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..aeaf95771 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..021e63240 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..5aa1cd97a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.french.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +public class FrenchNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return FrenchNumericWithUnit.ConnectorToken; + } + + public FrenchNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new FrenchNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..880c7f2f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..eee7acf80 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..6bc4d8a03 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,23 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } + + @Override + public String getConnectorToken() { + return null; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..e5cb6130c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..9bd27c939 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..102c5afd7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = GermanNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..6f4fd5364 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = GermanNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..1dd4a2bb9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = GermanNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = GermanNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..979b3c25f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(GermanNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..d066d80b6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,75 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.german.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class GermanNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected GermanNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit); + this.compoundUnitConnectorRegex = + Pattern.compile(GermanNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(GermanNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return GermanNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return GermanNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return ""; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..1e0a7e5d2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = GermanNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..7d6284cd0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = GermanNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..90f7a6dcf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousTemperatureUnitList; + } + + public static Map TemperatureSuffixList = new HashMap(GermanNumericWithUnit.TemperatureSuffixList); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..5c42ed9ca --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousVolumeUnitList; + } + + public static Map VolumeSuffixList = GermanNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..3a6be60a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousWeightUnitList; + } + + public static Map WeightSuffixList = GermanNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..1e9689c65 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..5f64c2d0a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..2bb68c241 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends GermanNumberWithUnitParserConfiguration { + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..78ec43eff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..203f80197 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.german.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.german.parsers.GermanNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; + +public abstract class GermanNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return ""; + } + + public GermanNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new GermanNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..635c6e7ce --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..de3e44700 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..c1714bdd4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..7e653cffa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..58b78955d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java new file mode 100644 index 000000000..9d0c67455 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java @@ -0,0 +1,103 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.ResolutionKey; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public abstract class AbstractNumberWithUnitModel implements IModel { + + private final Map extractorParserMap; + + public abstract String getModelTypeName(); + + protected Map getExtractorParserMap() { + return this.extractorParserMap; + } + + protected AbstractNumberWithUnitModel(Map extractorParserMap) { + this.extractorParserMap = extractorParserMap; + } + + @SuppressWarnings("unchecked") + public List parse(String query) { + + // Pre-process the query + query = QueryProcessor.preprocess(query, true); + + List extractionResults = new ArrayList(); + + try { + for (Map.Entry kv : extractorParserMap.entrySet()) { + IExtractor extractor = kv.getKey(); + IParser parser = kv.getValue(); + + List extractedResults = extractor.extract(query); + + List parsedResults = new ArrayList(); + + for (ExtractResult result : extractedResults) { + ParseResult parseResult = parser.parse(result); + if (parseResult.getValue() instanceof List) { + parsedResults.addAll((List)parseResult.getValue()); + } else { + parsedResults.add(parseResult); + } + } + + List modelResults = parsedResults.stream().map(o -> { + + SortedMap resolutionValues = new TreeMap(); + if (o.getValue() instanceof UnitValue) { + resolutionValues.put(ResolutionKey.Value, ((UnitValue)o.getValue()).number); + resolutionValues.put(ResolutionKey.Unit, ((UnitValue)o.getValue()).unit); + } else if (o.getValue() instanceof CurrencyUnitValue) { + resolutionValues.put(ResolutionKey.Value, ((CurrencyUnitValue)o.getValue()).number); + resolutionValues.put(ResolutionKey.Unit, ((CurrencyUnitValue)o.getValue()).unit); + resolutionValues.put(ResolutionKey.IsoCurrency, ((CurrencyUnitValue)o.getValue()).isoCurrency); + } else { + resolutionValues.put(ResolutionKey.Value, (String)o.getValue()); + } + + return new ModelResult( + o.getText(), + o.getStart(), + o.getStart() + o.getLength() - 1, + getModelTypeName(), + resolutionValues); + }).collect(Collectors.toList()); + + + for (ModelResult result : modelResults) { + boolean badd = true; + + for (ModelResult extractionResult : extractionResults) { + if (extractionResult.start == result.start && extractionResult.end == result.end) { + badd = false; + } + } + + if (badd) { + extractionResults.add(result); + } + } + } + } catch (Exception ex) { + // Nothing to do. Exceptions in parse should not break users of recognizers. + // No result. + ex.printStackTrace(); + } + + return extractionResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java new file mode 100644 index 000000000..c2cf881fe --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class AgeModel extends AbstractNumberWithUnitModel { + + public AgeModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "age"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java new file mode 100644 index 000000000..01f7e35fe --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class CurrencyModel extends AbstractNumberWithUnitModel { + + public CurrencyModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "currency"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java new file mode 100644 index 000000000..2907b2185 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +public class CurrencyUnitValue { + + public final String number; + public final String unit; + public final String isoCurrency; + + public CurrencyUnitValue(String number, String unit, String isoCurrency) { + this.number = number; + this.unit = unit; + this.isoCurrency = isoCurrency; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java new file mode 100644 index 000000000..f4ccf4d53 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class DimensionModel extends AbstractNumberWithUnitModel { + + public DimensionModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "dimension"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java new file mode 100644 index 000000000..5b6a8d6f4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +public class PrefixUnitResult { + public final int offset; + public final String unitStr; + + public PrefixUnitResult(int offset, String unitStr) { + this.offset = offset; + this.unitStr = unitStr; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java new file mode 100644 index 000000000..c41a10126 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class TemperatureModel extends AbstractNumberWithUnitModel { + + public TemperatureModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "temperature"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java new file mode 100644 index 000000000..b443c3e49 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +public class UnitValue { + + public final String number; + public final String unit; + + public UnitValue(String number, String unit) { + this.unit = unit; + this.number = number; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java new file mode 100644 index 000000000..3fc489686 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java @@ -0,0 +1,188 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.models.CurrencyUnitValue; +import com.microsoft.recognizers.text.numberwithunit.models.UnitValue; +import com.microsoft.recognizers.text.numberwithunit.utilities.DictionaryUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + + +public class BaseCurrencyParser implements IParser { + + private final BaseNumberWithUnitParserConfiguration config; + private final NumberWithUnitParser numberWithUnitParser; + + public BaseCurrencyParser(BaseNumberWithUnitParserConfiguration config) { + this.config = config; + this.numberWithUnitParser = new NumberWithUnitParser(config); + } + + @Override + public ParseResult parse(ExtractResult extResult) { + + ParseResult pr; + if (extResult.getData() instanceof List) { + return mergeCompoundUnit(extResult); + } else { + pr = numberWithUnitParser.parse(extResult); + UnitValue value = (UnitValue)pr.getValue(); + + String mainUnitIsoCode = null; + if (value != null && config.getCurrencyNameToIsoCodeMap().containsKey(value.unit)) { + mainUnitIsoCode = config.getCurrencyNameToIsoCodeMap().get(value.unit); + } + + if (mainUnitIsoCode == null || mainUnitIsoCode.isEmpty() || mainUnitIsoCode.startsWith(Constants.FAKE_ISO_CODE_PREFIX)) { + pr.setValue(new UnitValue(value.number, value.unit)); + } else { + pr.setValue(new CurrencyUnitValue(value.number, value.unit, mainUnitIsoCode)); + } + return pr; + } + } + + @SuppressWarnings("unchecked") + private ParseResult mergeCompoundUnit(ExtractResult compoundResult) { + List results = new ArrayList<>(); + List compoundUnit = (List)compoundResult.getData(); + + int count = 0; + ParseResult result = null; + // Make the default numberValue a constant to check if there is no Value. + double numberValue = Double.MIN_VALUE; + String mainUnitValue = ""; + String mainUnitIsoCode = ""; + String fractionUnitsString = ""; + + for (int idx = 0; idx < compoundUnit.size(); idx++) { + ExtractResult extractResult = compoundUnit.get(idx); + ParseResult parseResult = numberWithUnitParser.parse(extractResult); + Optional parseResultValue = Optional.ofNullable(parseResult.getValue() instanceof UnitValue ? (UnitValue)parseResult.getValue() : null); + String unitValue = parseResultValue.isPresent() ? parseResultValue.get().unit : ""; + + // Process a new group + if (count == 0) { + if (!extractResult.getType().equals(Constants.SYS_UNIT_CURRENCY)) { + continue; + } + + // Initialize a new result + result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + + mainUnitValue = unitValue; + if (parseResultValue.isPresent() && parseResultValue.get().number != null) { + numberValue = Double.parseDouble(parseResultValue.get().number); + } + + result.setResolutionStr(parseResult.getResolutionStr()); + mainUnitIsoCode = config.getCurrencyNameToIsoCodeMap().containsKey(unitValue) ? config.getCurrencyNameToIsoCodeMap().get(unitValue) : mainUnitIsoCode; + + // If the main unit can't be recognized, finish process this group. + if (mainUnitIsoCode == null || mainUnitIsoCode.isEmpty()) { + result.setValue(new UnitValue(getNumberValue(numberValue), mainUnitValue)); + results.add(result); + result = null; + continue; + } + + fractionUnitsString = config.getCurrencyFractionMapping().containsKey(mainUnitIsoCode) ? + config.getCurrencyFractionMapping().get(mainUnitIsoCode) : + fractionUnitsString; + } else { + + // Match pure number as fraction unit. + if (extractResult.getType().equals(Constants.SYS_NUM)) { + numberValue += (double)parseResult.getValue() * (1.0 / 100); + result.setLength(parseResult.getStart() + parseResult.getLength() - result.getStart()); + result.setResolutionStr(result.getResolutionStr() + " " + parseResult.getResolutionStr()); + count++; + continue; + } + + String fractionUnitCode = config.getCurrencyFractionCodeList().containsKey(unitValue) ? + config.getCurrencyFractionCodeList().get(unitValue) : + null; + + String unit = parseResultValue.isPresent() ? parseResultValue.get().unit : null; + Optional fractionNumValue = Optional.ofNullable(config.getCurrencyFractionNumMap().containsKey(unit) ? + config.getCurrencyFractionNumMap().get(unit) : + null); + + if (fractionUnitCode != null && !fractionUnitCode.isEmpty() && fractionNumValue.isPresent() && fractionNumValue.get() != 0 && + checkUnitsStringContains(fractionUnitCode, fractionUnitsString)) { + numberValue += Double.parseDouble(parseResultValue.get().number) * (1.0 / fractionNumValue.get()); + result.setLength(parseResult.getStart() + parseResult.getLength() - result.getStart()); + result.setResolutionStr(result.getResolutionStr() + " " + parseResult.getResolutionStr()); + } else { + // If the fraction unit doesn't match the main unit, finish process this group. + if (result != null) { + result = createCurrencyResult(result, mainUnitIsoCode, numberValue, mainUnitValue); + results.add(result); + result = null; + } + + count = 0; + idx -= 1; + numberValue = Double.MIN_VALUE; + continue; + } + } + + count++; + } + + if (result != null) { + result = createCurrencyResult(result, mainUnitIsoCode, numberValue, mainUnitValue); + results.add(result); + } + + resolveText(results, compoundResult.getText(), compoundResult.getStart()); + + return new ParseResult(null, null, null, null, null, results, null); + } + + private boolean checkUnitsStringContains(String fractionUnitCode, String fractionUnitsString) { + Map unitsMap = new HashMap(); + DictionaryUtils.bindUnitsString(unitsMap, "", fractionUnitsString); + return unitsMap.containsKey(fractionUnitCode); + } + + private void resolveText(List prs, String source, int bias) { + for (ParseResult parseResult : prs) { + if (parseResult.getStart() != null && parseResult.getLength() != null) { + int start = parseResult.getStart() - bias; + parseResult.setText(source.substring(start, start + parseResult.getLength())); + prs.set(prs.indexOf(parseResult), parseResult); + } + } + } + + private String getNumberValue(double numberValue) { + if (numberValue == Double.MIN_VALUE) { + return null; + } else { + java.text.NumberFormat numberFormat = java.text.NumberFormat.getInstance(); + numberFormat.setMinimumFractionDigits(0); + numberFormat.setGroupingUsed(false); + return numberFormat.format(numberValue); + } + } + + private ParseResult createCurrencyResult(ParseResult result, String mainUnitIsoCode, Double numberValue, String mainUnitValue) { + if (mainUnitIsoCode == null || mainUnitIsoCode.isEmpty() || + mainUnitIsoCode.startsWith(Constants.FAKE_ISO_CODE_PREFIX)) { + result.setValue(new UnitValue(getNumberValue(numberValue), mainUnitValue)); + } else { + result.setValue(new CurrencyUnitValue(getNumberValue(numberValue), mainUnitValue, mainUnitIsoCode)); + } + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java new file mode 100644 index 000000000..bbadb603e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.numberwithunit.Constants; + +public class BaseMergedUnitParser implements IParser { + + protected final BaseNumberWithUnitParserConfiguration config; + private final NumberWithUnitParser numberWithUnitParser; + + public BaseMergedUnitParser(BaseNumberWithUnitParserConfiguration config) { + this.config = config; + this.numberWithUnitParser = new NumberWithUnitParser(config); + } + + @Override + public ParseResult parse(ExtractResult extResult) { + // For now only currency model recognizes compound units. + if (extResult.getType().equals(Constants.SYS_UNIT_CURRENCY)) { + return new BaseCurrencyParser(config).parse(extResult); + } else { + return numberWithUnitParser.parse(extResult); + } + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..84fe89e8f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java @@ -0,0 +1,72 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseCurrency; +import com.microsoft.recognizers.text.numberwithunit.utilities.DictionaryUtils; + +import java.util.HashMap; +import java.util.Map; + +public abstract class BaseNumberWithUnitParserConfiguration implements INumberWithUnitParserConfiguration { + private final Map unitMap; + private final Map currencyFractionNumMap; + private final Map currencyFractionMapping; + private final CultureInfo cultureInfo; + private final Map currencyNameToIsoCodeMap; + private final Map currencyFractionCodeList; + + @Override + public Map getUnitMap() { + return this.unitMap; + } + + @Override + public Map getCurrencyFractionNumMap() { + return this.currencyFractionNumMap; + } + + @Override + public Map getCurrencyFractionMapping() { + return this.currencyFractionMapping; + } + + @Override + public Map getCurrencyNameToIsoCodeMap() { + return this.currencyNameToIsoCodeMap; + } + + @Override + public Map getCurrencyFractionCodeList() { + return this.currencyFractionCodeList; + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public abstract IParser getInternalNumberParser(); + + @Override + public abstract IExtractor getInternalNumberExtractor(); + + @Override + public abstract String getConnectorToken(); + + protected BaseNumberWithUnitParserConfiguration(CultureInfo ci) { + this.cultureInfo = ci; + this.unitMap = new HashMap<>(); + this.currencyFractionNumMap = BaseCurrency.CurrencyFractionalRatios; + this.currencyFractionMapping = BaseCurrency.CurrencyFractionMapping; + this.currencyNameToIsoCodeMap = new HashMap<>(); + this.currencyFractionCodeList = new HashMap<>(); + } + + @Override + public void bindDictionary(Map dictionary) { + DictionaryUtils.bindDictionary(dictionary, getUnitMap()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..11829a00e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java @@ -0,0 +1,30 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public interface INumberWithUnitParserConfiguration { + + Map getUnitMap(); + + Map getCurrencyFractionNumMap(); + + Map getCurrencyFractionMapping(); + + Map getCurrencyNameToIsoCodeMap(); + + Map getCurrencyFractionCodeList(); + + CultureInfo getCultureInfo(); + + IParser getInternalNumberParser(); + + IExtractor getInternalNumberExtractor(); + + String getConnectorToken(); + + void bindDictionary(Map dictionary); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java new file mode 100644 index 000000000..a57e16410 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java @@ -0,0 +1,143 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.models.UnitValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class NumberWithUnitParser implements IParser { + + protected final INumberWithUnitParserConfiguration config; + + public NumberWithUnitParser(INumberWithUnitParserConfiguration config) { + this.config = config; + } + + @Override + public ParseResult parse(ExtractResult extResult) { + + Map unitMap = this.config.getUnitMap(); + String connectorToken = this.config.getConnectorToken(); + ParseResult ret = new ParseResult(extResult); + ExtractResult numberResult; + ExtractResult halfResult; + + if (extResult.getData() instanceof ExtractResult) { + numberResult = (ExtractResult)extResult.getData(); + halfResult = null; + } else if (extResult.getType().equals(Constants.SYS_NUM)) { + ret.setValue(config.getInternalNumberParser().parse(extResult).getValue()); + return ret; + } else if (extResult.getData() instanceof List) { + Object data = extResult.getData(); + List dataList = new ArrayList(); + for (Object d : (List)data) { + dataList.add((ExtractResult)d); + } + if (dataList.size() >= 2) { + numberResult = dataList.get(0); + halfResult = dataList.get(1); + } else { + numberResult = dataList.get(0); + halfResult = null; + } + } else { + // if there is no unitResult, means there is just unit + numberResult = new ExtractResult(-1, 0, null, null, null); + halfResult = null; + } + + // key contains units + String key = extResult.getText(); + StringBuilder unitKeyBuild = new StringBuilder(); + List unitKeys = new ArrayList<>(); + + for (int i = 0; i <= key.length(); i++) { + + if (i == key.length()) { + if (unitKeyBuild.length() != 0) { + addIfNotContained(unitKeys, unitKeyBuild.toString().trim()); + } + } else if (i == numberResult.getStart()) { + // numberResult.start is a relative position + if (unitKeyBuild.length() != 0) { + addIfNotContained(unitKeys, unitKeyBuild.toString().trim()); + unitKeyBuild = new StringBuilder(); + } + i = numberResult.getStart() + numberResult.getLength() - 1; + } else { + unitKeyBuild.append(key.charAt(i)); + } + } + + /* Unit type depends on last unit in suffix.*/ + String lastUnit = unitKeys.get(unitKeys.size() - 1); + if (halfResult != null) { + lastUnit = lastUnit.substring(0, lastUnit.length() - halfResult.getText().length()); + } + String normalizedLastUnit = lastUnit.toLowerCase(); + + if (connectorToken != null && !connectorToken.isEmpty() && normalizedLastUnit.startsWith(connectorToken)) { + normalizedLastUnit = normalizedLastUnit.substring(connectorToken.length()).trim(); + lastUnit = lastUnit.substring(connectorToken.length()).trim(); + } + + if (key != null && !key.isEmpty() && unitMap != null) { + + String unitValue = null; + + if (unitMap.containsKey(lastUnit)) { + unitValue = unitMap.get(lastUnit); + } else if (unitMap.containsKey(normalizedLastUnit)) { + unitValue = unitMap.get(normalizedLastUnit); + } + + if (unitValue != null) { + + ParseResult numValue = numberResult.getText() == null || numberResult.getText().isEmpty() ? + null : + this.config.getInternalNumberParser().parse(numberResult); + String resolutionStr = numValue != null ? numValue.getResolutionStr() : null; + + if (halfResult != null) { + ParseResult halfValue = this.config.getInternalNumberParser().parse(halfResult); + String tmp = halfValue != null ? halfValue.getResolutionStr() : null; + if (tmp != null) { + resolutionStr += tmp.substring(1, tmp.length()); + } + } + + ret.setValue(new UnitValue(resolutionStr, unitValue)); + ret.setResolutionStr(String.format("%s %s", resolutionStr != null ? resolutionStr : "", unitValue).trim()); + } + } + + if (ret != null) { + ret.setText(ret.getText().toLowerCase(Locale.ROOT)); + } + + return ret; + } + + private void addIfNotContained(List unitKeys, String unit) { + + boolean add = true; + for (String unitKey : unitKeys) { + if (unitKey.contains(unit)) { + add = false; + break; + } + } + + if (add) { + unitKeys.add(unit); + } + } + +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..f18980218 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = PortugueseNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..d05d18356 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = PortugueseNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..e1d089d77 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = PortugueseNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = PortugueseNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..b3522d05e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(PortugueseNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..918f68a43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = PortugueseNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..81c194a77 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,76 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.portuguese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class PortugueseNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected PortugueseNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit);; + this.compoundUnitConnectorRegex = + Pattern.compile(PortugueseNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(PortugueseNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return PortugueseNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return PortugueseNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return PortugueseNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..28859340d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = PortugueseNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..fc3acdfa2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map TemperatureSuffixList = new HashMap(PortugueseNumericWithUnit.TemperatureSuffixList); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..cd30a3c05 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map VolumeSuffixList = PortugueseNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..1e55a2cfb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map WeightSuffixList = PortugueseNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..eed9702c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..7332b5b3c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..6b8df0ede --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..fc4c04d29 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..66fe22b0e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..295dca7d3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.number.portuguese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.portuguese.parsers.PortugueseNumberParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +public class PortugueseNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return PortugueseNumericWithUnit.ConnectorToken; + } + + public PortugueseNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(NumberMode.Default); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new PortugueseNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..3134499f5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..7f3b46c5c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..373a2480a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..ddce7b5b6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java new file mode 100644 index 000000000..55a95d4a6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java @@ -0,0 +1,281 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class BaseCurrency { + + public static final ImmutableMap CurrencyFractionMapping = ImmutableMap.builder() + .put("CNY", "FEN|JIAO") + .put("__D", "CENT") + .put("RUB", "KOPEK") + .put("AFN", "PUL") + .put("EUR", "CENT|KWARTJE|DUBBELTJE|STUIVER") + .put("ALL", "QINDARKE") + .put("_ALP", "PENNY") + .put("GBP", "PENNY") + .put("_GGP", "PENNY") + .put("DZD", "SANTEEM") + .put("AOA", "CENTIMO") + .put("ARS", "CENTAVO") + .put("AMD", "LUMA") + .put("AWG", "CENT") + .put("_AP", "PENNY") + .put("SHP", "PENNY") + .put("AUD", "CENT") + .put("AZN", "QƏPIK") + .put("BSD", "CENT") + .put("BHD", "FILS") + .put("BDT", "POISHA") + .put("BBD", "CENT") + .put("BYN", "KAPYEYKA") + .put("BZD", "CENT") + .put("XOF", "CENTIME") + .put("BMD", "CENT") + .put("BTN", "CHETRUM") + .put("INR", "PAISA") + .put("BOB", "CENTAVO") + .put("USD", "CENT") + .put("BAM", "FENING") + .put("BWP", "THEBE") + .put("BRL", "CENTAVO") + .put("_BD", "CENT") + .put("BND", "SEN") + .put("SGD", "CENT") + .put("BGN", "STOTINKA") + .put("BIF", "CENTIME") + .put("KHR", "SEN") + .put("XAF", "CENTIME") + .put("CAD", "CENT") + .put("CVE", "CENTAVO") + .put("KYD", "CENT") + .put("CLP", "CENTAVO") + .put("COP", "CENTAVO") + .put("KMF", "CENTIME") + .put("CDF", "CENTIME") + .put("NZD", "CENT") + .put("_CKD", "CENT") + .put("CRC", "CENTIMO") + .put("HRK", "LIPA") + .put("CUC", "CENTAVO") + .put("CUP", "CENTAVO") + .put("CZK", "HALER") + .put("DKK", "ØRE") + .put("DJF", "CENTIME") + .put("DOP", "CENTAVO") + .put("EGP", "PIASTRE") + .put("ERN", "CENT") + .put("ETB", "SANTIM") + .put("FKP", "PENNY") + .put("_FOK", "OYRA") + .put("FJD", "CENT") + .put("XPF", "CENTIME") + .put("GMD", "BUTUT") + .put("GEL", "TETRI") + .put("GHS", "PESEWA") + .put("GIP", "PENNY") + .put("GTQ", "CENTAVO") + .put("GNF", "CENTIME") + .put("GYD", "CENT") + .put("HTG", "CENTIME") + .put("HNL", "CENTAVO") + .put("HKD", "CENT") + .put("HUF", "FILLER") + .put("ISK", "EYRIR") + .put("IDR", "SEN") + .put("IRR", "DINAR") + .put("IQD", "FILS") + .put("IMP", "PENNY") + .put("ILS", "AGORA") + .put("JMD", "CENT") + .put("JPY", "SEN") + .put("JEP", "PENNY") + .put("JOD", "PIASTRE") + .put("KZT", "TIIN") + .put("KES", "CENT") + .put("_KID", "CENT") + .put("KPW", "CHON") + .put("KRW", "JEON") + .put("KWD", "FILS") + .put("KGS", "TYIYN") + .put("LAK", "ATT") + .put("LBP", "PIASTRE") + .put("LSL", "SENTE") + .put("ZAR", "CENT") + .put("LRD", "CENT") + .put("LYD", "DIRHAM") + .put("CHF", "RAPPEN") + .put("MOP", "AVO") + .put("MKD", "DENI") + .put("MGA", "IRAIMBILANJA") + .put("MWK", "TAMBALA") + .put("MYR", "SEN") + .put("MVR", "LAARI") + .put("MRO", "KHOUMS") + .put("MUR", "CENT") + .put("MXN", "CENTAVO") + .put("_MD", "CENT") + .put("MDL", "BAN") + .put("MNT", "MONGO") + .put("MAD", "CENTIME") + .put("MZN", "CENTAVO") + .put("MMK", "PYA") + .put("NAD", "CENT") + .put("_ND", "CENT") + .put("NPR", "PAISA") + .put("NIO", "CENTAVO") + .put("NGN", "KOBO") + .put("_NID", "CENT") + .put("TRY", "KURUS") + .put("NOK", "ØRE") + .put("OMR", "BAISA") + .put("PKR", "PAISA") + .put("_PD", "CENT") + .put("PAB", "CENTESIMO") + .put("PGK", "TOEA") + .put("PYG", "CENTIMO") + .put("PEN", "CENTIMO") + .put("_PND", "CENT") + .put("PLN", "GROSZ") + .put("QAR", "DIRHAM") + .put("RON", "BAN") + .put("RWF", "CENTIME") + .put("WST", "SENE") + .put("STD", "CENTIMO") + .put("SAR", "HALALA") + .put("RSD", "PARA") + .put("SCR", "CENT") + .put("SLL", "CENT") + .put("SBD", "CENT") + .put("SOS", "CENT") + .put("_SS", "CENT") + .put("_SP", "PENNY") + .put("SSP", "PIASTRE") + .put("LKR", "CENT") + .put("SDG", "PIASTRE") + .put("SRD", "CENT") + .put("SZL", "CENT") + .put("SEK", "ORE") + .put("SYP", "PIASTRE") + .put("TWD", "CENT") + .put("TJS", "DIRAM") + .put("TZS", "CENT") + .put("THB", "SATANG") + .put("PRB", "KOPEK") + .put("TTD", "CENT") + .put("_TP", "PENNY") + .put("TND", "MILLIME") + .put("TMT", "TENNESI") + .put("TVD", "CENT") + .put("UGX", "CENT") + .put("UAH", "KOPIYKA") + .put("AED", "FILS") + .put("UYU", "CENTESIMO") + .put("VEF", "CENTIMO") + .put("YER", "FILS") + .put("ZMW", "NGWEE") + .build(); + + public static final ImmutableMap CurrencyFractionalRatios = ImmutableMap.builder() + .put("Kopek", 100L) + .put("Pul", 100L) + .put("Cent", 100L) + .put("Qindarkë", 100L) + .put("Penny", 100L) + .put("Santeem", 100L) + .put("Cêntimo", 100L) + .put("Centavo", 100L) + .put("Luma", 100L) + .put("Qəpik", 100L) + .put("Fils", 1000L) + .put("Poisha", 100L) + .put("Kapyeyka", 100L) + .put("Centime", 100L) + .put("Chetrum", 100L) + .put("Paisa", 100L) + .put("Fening", 100L) + .put("Thebe", 100L) + .put("Sen", 100L) + .put("Stotinka", 100L) + .put("Jiao", 10L) + .put("Fen", 100L) + .put("Céntimo", 100L) + .put("Lipa", 100L) + .put("Haléř", 100L) + .put("Øre", 100L) + .put("Piastre", 100L) + .put("Santim", 100L) + .put("Oyra", 100L) + .put("Butut", 100L) + .put("Tetri", 100L) + .put("Pesewa", 100L) + .put("Fillér", 100L) + .put("Eyrir", 100L) + .put("Dinar", 100L) + .put("Agora", 100L) + .put("Tïın", 100L) + .put("Chon", 100L) + .put("Jeon", 100L) + .put("Tyiyn", 100L) + .put("Att", 100L) + .put("Sente", 100L) + .put("Dirham", 1000L) + .put("Rappen", 100L) + .put("Avo", 100L) + .put("Deni", 100L) + .put("Iraimbilanja", 5L) + .put("Tambala", 100L) + .put("Laari", 100L) + .put("Khoums", 5L) + .put("Ban", 100L) + .put("Möngö", 100L) + .put("Pya", 100L) + .put("Kobo", 100L) + .put("Kuruş", 100L) + .put("Baisa", 1000L) + .put("Centésimo", 100L) + .put("Toea", 100L) + .put("Sentimo", 100L) + .put("Grosz", 100L) + .put("Sene", 100L) + .put("Halala", 100L) + .put("Para", 100L) + .put("Öre", 100L) + .put("Diram", 100L) + .put("Satang", 100L) + .put("Seniti", 100L) + .put("Millime", 1000L) + .put("Tennesi", 100L) + .put("Kopiyka", 100L) + .put("Tiyin", 100L) + .put("Hào", 10L) + .put("Ngwee", 100L) + .put("Kwartje", 4L) + .put("Dubbeltje", 10L) + .put("Stuiver", 20L) + .build(); + + public static final ImmutableMap NonStandardFractionalSubunits = ImmutableMap.builder() + .put("JOD", 1000L) + .put("KWD", 1000L) + .put("BHD", 1000L) + .put("OMR", 1000L) + .put("YDD", 1000L) + .put("TND", 1000L) + .put("MRO", 5L) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java new file mode 100644 index 000000000..fddeaad76 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +public class BaseUnits { + + public static final String HourRegex = "(?00|01|02|03|04|05|06|07|08|09|0|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|1|2|3|4|5|6|7|8|9)(h)?"; + + public static final String MinuteRegex = "(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)(?!\\d)"; + + public static final String SecondRegex = "(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)"; + + public static final String PmNonUnitRegex = "({HourRegex}\\s*:\\s*{MinuteRegex}(\\s*:\\s*{SecondRegex})?\\s*pm)" + .replace("{HourRegex}", HourRegex) + .replace("{MinuteRegex}", MinuteRegex) + .replace("{SecondRegex}", SecondRegex); + + public static final String AmbiguousTimeTerm = "pm"; + + public static final String AmbiguousUnitNumberMultiplierRegex = "(\\s([Kk]|mil))"; + + public static final String SingleCharUnitRegex = "^\\b(c|f|g|k|l|m|s)(\\s*\\.|\\b)$"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java new file mode 100644 index 000000000..d4086e071 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java @@ -0,0 +1,639 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class ChineseNumericWithUnit { + + public static final List AgeAmbiguousValues = Arrays.asList("岁"); + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "岁|周岁") + .put("Month", "个月大|月大") + .put("Week", "周大") + .put("Day", "天大") + .build(); + + public static final String BuildPrefix = ""; + + public static final String BuildSuffix = ""; + + public static final String ConnectorToken = ""; + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Afghan afghani", "阿富汗尼") + .put("Pul", "普尔") + .put("Euro", "欧元") + .put("Cent", "美分") + .put("Albanian lek", "阿尔巴尼亚列克|列克") + .put("Angolan kwanza", "安哥拉宽扎|宽扎") + .put("Armenian dram", "亚美尼亚德拉姆") + .put("Aruban florin", "阿鲁巴弗罗林|阿鲁巴币") + .put("Bangladeshi taka", "塔卡|孟加拉塔卡") + .put("Paisa", "派萨|帕萨") + .put("Bhutanese ngultrum", "不丹努尔特鲁姆|不丹努扎姆|努扎姆") + .put("Chetrum", "切特鲁姆") + .put("Bolivian boliviano", "玻利维亚诺|玻利维亚币") + .put("Bosnia and Herzegovina convertible mark", "波斯尼亚和黑塞哥维那可兑换马克|波赫可兑换马克") + .put("Botswana pula", "博茨瓦纳普拉|普拉") + .put("Thebe", "thebe") + .put("Brazilian real", "巴西雷亚尔") + .put("Bulgarian lev", "保加利亚列弗|保加利亚列瓦") + .put("Stotinka", "斯托丁卡") + .put("Cambodian riel", "瑞尔") + .put("Cape Verdean escudo", "佛得角埃斯库多|维德角埃斯库多") + .put("Croatian kuna", "克罗地亚库纳|克罗地亚库那|克罗埃西亚库纳") + .put("Lipa", "利巴") + .put("Eritrean nakfa", "厄立特里亚纳克法") + .put("Ethiopian birr", "埃塞俄比亚比尔|埃塞俄比亚元") + .put("Gambian dalasi", "冈比亚达拉西|甘比亚达拉西") + .put("Butut", "布达|布图") + .put("Georgian lari", "格鲁吉亚拉里") + .put("Tetri", "特特里|泰特里") + .put("Ghanaian cedi", "塞地|加纳塞地") + .put("Pesewa", "比塞瓦") + .put("Guatemalan quetzal", "瓜地马拉格查尔") + .put("Haitian gourde", "海地古德") + .put("Honduran lempira", "洪都拉斯伦皮拉") + .put("Hungarian forint", "匈牙利福林|匈牙利货币|匈牙利福林币") + .put("Iranian rial", "伊朗里亚尔|伊朗莱尔") + .put("Yemeni rial", "叶门莱尔|叶门里亚尔") + .put("Israeli new shekel", "₪|ils|以色列币|以色列新克尔|谢克尔") + .put("Japanese yen", "日元|日本元|日币|日圆") + .put("Sen", "日本銭") + .put("Kazakhstani tenge", "哈萨克斯坦坚戈") + .put("Kenyan shilling", "肯尼亚先令") + .put("North Korean won", "朝鲜圆|朝鲜元") + .put("South Korean won", "韩元|韩圆") + .put("Korean won", "₩") + .put("Kyrgyzstani som", "吉尔吉斯斯坦索姆") + .put("Lao kip", "基普|老挝基普|老挝币") + .put("Att", "att") + .put("Lesotho loti", "莱索托洛提|莱索托马洛蒂") + .put("South African rand", "南非兰特") + .put("Macedonian denar", "马其顿代纳尔|马其顿币|代纳尔") + .put("Deni", "第尼") + .put("Malagasy ariary", "马达加斯加阿里亚里") + .put("Iraimbilanja", "伊莱姆比拉贾") + .put("Malawian kwacha", "马拉威克瓦查") + .put("Tambala", "坦巴拉") + .put("Malaysian ringgit", "马来西亚币|马币|马来西亚林吉特") + .put("Mauritanian ouguiya", "毛里塔尼亚乌吉亚") + .put("Khoums", "库姆斯") + .put("Mozambican metical", "莫桑比克梅蒂卡尔|梅蒂卡尔") + .put("Burmese kyat", "缅甸元|缅元") + .put("Pya", "缅分") + .put("Nigerian naira", "尼日利亚奈拉|尼日利亚币|奈拉") + .put("Kobo", "考包") + .put("Turkish lira", "土耳其里拉") + .put("Kuruş", "库鲁") + .put("Omani rial", "阿曼里亚尔|阿曼莱尔") + .put("Panamanian balboa", "巴拿马巴波亚") + .put("Centesimo", "意大利分|乌拉圭分|巴拿马分") + .put("Papua New Guinean kina", "基那") + .put("Toea", "托亚|托伊") + .put("Peruvian sol", "秘鲁索尔") + .put("Polish złoty", "波兰币|波兰兹罗提|兹罗提") + .put("Grosz", "格罗希") + .put("Qatari riyal", "卡达里亚尔") + .put("Saudi riyal", "沙特里亚尔") + .put("Riyal", "里亚尔") + .put("Dirham", "迪拉姆") + .put("Halala", "哈拉") + .put("Samoan tālā", "萨摩亚塔拉") + .put("Sierra Leonean leone", "塞拉利昂利昂|利昂") + .put("Peseta", "比塞塔|西班牙比塞塔|西班牙币") + .put("Swazi lilangeni", "斯威士兰里兰吉尼|兰吉尼") + .put("Tajikistani somoni", "塔吉克斯坦索莫尼") + .put("Thai baht", "泰铢|泰元") + .put("Satang", "萨当") + .put("Tongan paʻanga", "汤加潘加|潘加") + .put("Ukrainian hryvnia", "乌克兰格里夫纳|格里夫纳") + .put("Vanuatu vatu", "瓦努阿图瓦图") + .put("Vietnamese dong", "越南盾") + .put("Indonesian rupiah", "印度尼西亚盾") + .put("Netherlands guilder", "荷兰盾|荷属安的列斯盾|列斯盾") + .put("Surinam florin", "苏里南盾") + .put("Guilder", "盾") + .put("Zambian kwacha", "赞比亚克瓦查") + .put("Moroccan dirham", "摩洛哥迪拉姆") + .put("United Arab Emirates dirham", "阿联酋迪拉姆") + .put("Azerbaijani manat", "阿塞拜疆马纳特") + .put("Turkmenistan manat", "土库曼马纳特") + .put("Manat", "马纳特") + .put("Somali shilling", "索马里先令|索马利先令") + .put("Somaliland shilling", "索马里兰先令") + .put("Tanzanian shilling", "坦桑尼亚先令") + .put("Ugandan shilling", "乌干达先令") + .put("Romanian leu", "罗马尼亚列伊") + .put("Moldovan leu", "摩尔多瓦列伊") + .put("Leu", "列伊") + .put("Ban", "巴尼") + .put("Nepalese rupee", "尼泊尔卢比") + .put("Pakistani rupee", "巴基斯坦卢比") + .put("Indian rupee", "印度卢比") + .put("Seychellois rupee", "塞舌尔卢比") + .put("Mauritian rupee", "毛里求斯卢比") + .put("Maldivian rufiyaa", "马尔代夫卢比") + .put("Sri Lankan rupee", "斯里兰卡卢比") + .put("Rupee", "卢比") + .put("Czech koruna", "捷克克朗") + .put("Danish krone", "丹麦克朗|丹麦克郎") + .put("Norwegian krone", "挪威克朗") + .put("Faroese króna", "法罗克朗") + .put("Icelandic króna", "冰岛克朗") + .put("Swedish krona", "瑞典克朗") + .put("Krone", "克朗") + .put("Øre", "奥依拉|奥拉|埃利") + .put("West African CFA franc", "非共体法郎") + .put("Central African CFA franc", "中非法郎|中非金融合作法郎") + .put("Comorian franc", "科摩罗法郎") + .put("Congolese franc", "刚果法郎") + .put("Burundian franc", "布隆迪法郎") + .put("Djiboutian franc", "吉布提法郎") + .put("CFP franc", "太平洋法郎") + .put("Guinean franc", "几内亚法郎") + .put("Swiss franc", "瑞士法郎") + .put("Rwandan franc", "卢旺达法郎") + .put("Belgian franc", "比利时法郎") + .put("Rappen", "瑞士分|瑞士生丁") + .put("Franc", "法郎") + .put("Centime", "生丁|仙士") + .put("Russian ruble", "俄国卢布|俄罗斯卢布") + .put("Transnistrian ruble", "德涅斯特卢布") + .put("Belarusian ruble", "白俄罗斯卢布") + .put("Kopek", "戈比") + .put("Ruble", "卢布") + .put("Algerian dinar", "阿尔及利亚第纳尔") + .put("Bahraini dinar", "巴林第纳尔") + .put("Iraqi dinar", "伊拉克第纳尔") + .put("Jordanian dinar", "约旦第纳尔") + .put("Kuwaiti dinar", "科威特第纳尔|科威特币") + .put("Libyan dinar", "利比亚第纳尔") + .put("Serbian dinar", "塞尔维亚第纳尔|塞尔维亚币") + .put("Tunisian dinar", "突尼斯第纳尔") + .put("Dinar", "第纳尔") + .put("Fils", "费尔") + .put("Para", "帕拉") + .put("Millime", "米利姆") + .put("Argentine peso", "阿根廷比索") + .put("Chilean peso", "智利比索") + .put("Colombian peso", "哥伦比亚比索") + .put("Cuban peso", "古巴比索") + .put("Dominican peso", "多米尼加比索") + .put("Mexican peso", "墨西哥比索") + .put("Philippine peso", "菲律宾比索") + .put("Uruguayan peso", "乌拉圭比索") + .put("Peso", "比索") + .put("Centavo", "仙|菲辅币") + .put("Alderney pound", "奥尔德尼镑") + .put("British pound", "英镑") + .put("Guernsey pound", "根西镑") + .put("Saint Helena pound", "圣赫勒拿镑") + .put("Egyptian pound", "埃及镑") + .put("Falkland Islands pound", "福克兰镑") + .put("Gibraltar pound", "直布罗陀镑") + .put("Manx pound", "马恩岛镑") + .put("Jersey pound", "泽西岛镑") + .put("Lebanese pound", "黎巴嫩镑") + .put("South Sudanese pound", "南苏丹镑") + .put("Sudanese pound", "苏丹镑") + .put("Syrian pound", "叙利亚镑") + .put("Pence", "便士") + .put("Shilling", "先令") + .put("United States dollar", "美元|美金|美圆") + .put("East Caribbean dollar", "东加勒比元") + .put("Australian dollar", "澳大利亚元|澳元") + .put("Bahamian dollar", "巴哈马元") + .put("Barbadian dollar", "巴巴多斯元") + .put("Belize dollar", "伯利兹元") + .put("Bermudian dollar", "百慕大元") + .put("Brunei dollar", "文莱元") + .put("Singapore dollar", "新加坡元|新元") + .put("Canadian dollar", "加元|加拿大元") + .put("Cayman Islands dollar", "开曼岛元") + .put("New Zealand dollar", "新西兰元|纽元") + .put("Cook Islands dollar", "库克群岛元") + .put("Fijian dollar", "斐济元|斐币") + .put("Guyanese dollar", "圭亚那元") + .put("Hong Kong dollar", "蚊|港元|港圆|港币") + .put("Macau Pataca", "澳门币|澳门元") + .put("New Taiwan dollar", "箍|新台币|台币") + .put("Jamaican dollar", "牙买加元") + .put("Kiribati dollar", "吉里巴斯元") + .put("Liberian dollar", "利比里亚元") + .put("Namibian dollar", "纳米比亚元") + .put("Surinamese dollar", "苏里南元") + .put("Trinidad and Tobago dollar", "特立尼达多巴哥元") + .put("Tuvaluan dollar", "吐瓦鲁元") + .put("Chinese yuan", "人民币|人民币元|元人民币|块钱|块|元|圆") + .put("Fen", "分钱|分") + .put("Jiao", "毛钱|毛|角钱|角") + .put("Finnish markka", "芬兰马克") + .put("Penni", "盆尼") + .build(); + + public static final ImmutableMap CurrencyNameToIsoCodeMap = ImmutableMap.builder() + .put("Afghan afghani", "AFN") + .put("Euro", "EUR") + .put("Albanian lek", "ALL") + .put("Angolan kwanza", "AOA") + .put("Armenian dram", "AMD") + .put("Aruban florin", "AWG") + .put("Bangladeshi taka", "BDT") + .put("Bhutanese ngultrum", "BTN") + .put("Bolivian boliviano", "BOB") + .put("Bosnia and Herzegovina convertible mark", "BAM") + .put("Botswana pula", "BWP") + .put("Brazilian real", "BRL") + .put("Bulgarian lev", "BGN") + .put("Cambodian riel", "KHR") + .put("Cape Verdean escudo", "CVE") + .put("Costa Rican colón", "CRC") + .put("Croatian kuna", "HRK") + .put("Czech koruna", "CZK") + .put("Eritrean nakfa", "ERN") + .put("Ethiopian birr", "ETB") + .put("Gambian dalasi", "GMD") + .put("Georgian lari", "GEL") + .put("Ghanaian cedi", "GHS") + .put("Guatemalan quetzal", "GTQ") + .put("Haitian gourde", "HTG") + .put("Honduran lempira", "HNL") + .put("Hungarian forint", "HUF") + .put("Iranian rial", "IRR") + .put("Yemeni rial", "YER") + .put("Israeli new shekel", "ILS") + .put("Japanese yen", "JPY") + .put("Kazakhstani tenge", "KZT") + .put("Kenyan shilling", "KES") + .put("North Korean won", "KPW") + .put("South Korean won", "KRW") + .put("Kyrgyzstani som", "KGS") + .put("Lao kip", "LAK") + .put("Lesotho loti", "LSL") + .put("South African rand", "ZAR") + .put("Macanese pataca", "MOP") + .put("Macedonian denar", "MKD") + .put("Malagasy ariary", "MGA") + .put("Malawian kwacha", "MWK") + .put("Malaysian ringgit", "MYR") + .put("Mauritanian ouguiya", "MRO") + .put("Mongolian tögrög", "MNT") + .put("Mozambican metical", "MZN") + .put("Burmese kyat", "MMK") + .put("Nicaraguan córdoba", "NIO") + .put("Nigerian naira", "NGN") + .put("Turkish lira", "TRY") + .put("Omani rial", "OMR") + .put("Panamanian balboa", "PAB") + .put("Papua New Guinean kina", "PGK") + .put("Paraguayan guaraní", "PYG") + .put("Peruvian sol", "PEN") + .put("Polish złoty", "PLN") + .put("Qatari riyal", "QAR") + .put("Saudi riyal", "SAR") + .put("Samoan tālā", "WST") + .put("São Tomé and Príncipe dobra", "STD") + .put("Sierra Leonean leone", "SLL") + .put("Swazi lilangeni", "SZL") + .put("Tajikistani somoni", "TJS") + .put("Thai baht", "THB") + .put("Ukrainian hryvnia", "UAH") + .put("Vanuatu vatu", "VUV") + .put("Venezuelan bolívar", "VEF") + .put("Zambian kwacha", "ZMW") + .put("Moroccan dirham", "MAD") + .put("United Arab Emirates dirham", "AED") + .put("Azerbaijani manat", "AZN") + .put("Turkmenistan manat", "TMT") + .put("Somali shilling", "SOS") + .put("Tanzanian shilling", "TZS") + .put("Ugandan shilling", "UGX") + .put("Romanian leu", "RON") + .put("Moldovan leu", "MDL") + .put("Nepalese rupee", "NPR") + .put("Pakistani rupee", "PKR") + .put("Indian rupee", "INR") + .put("Seychellois rupee", "SCR") + .put("Mauritian rupee", "MUR") + .put("Maldivian rufiyaa", "MVR") + .put("Sri Lankan rupee", "LKR") + .put("Indonesian rupiah", "IDR") + .put("Danish krone", "DKK") + .put("Norwegian krone", "NOK") + .put("Icelandic króna", "ISK") + .put("Swedish krona", "SEK") + .put("West African CFA franc", "XOF") + .put("Central African CFA franc", "XAF") + .put("Comorian franc", "KMF") + .put("Congolese franc", "CDF") + .put("Burundian franc", "BIF") + .put("Djiboutian franc", "DJF") + .put("CFP franc", "XPF") + .put("Guinean franc", "GNF") + .put("Swiss franc", "CHF") + .put("Rwandan franc", "RWF") + .put("Russian ruble", "RUB") + .put("Transnistrian ruble", "PRB") + .put("Belarusian ruble", "BYN") + .put("Algerian dinar", "DZD") + .put("Bahraini dinar", "BHD") + .put("Iraqi dinar", "IQD") + .put("Jordanian dinar", "JOD") + .put("Kuwaiti dinar", "KWD") + .put("Libyan dinar", "LYD") + .put("Serbian dinar", "RSD") + .put("Tunisian dinar", "TND") + .put("Argentine peso", "ARS") + .put("Chilean peso", "CLP") + .put("Colombian peso", "COP") + .put("Cuban convertible peso", "CUC") + .put("Cuban peso", "CUP") + .put("Dominican peso", "DOP") + .put("Mexican peso", "MXN") + .put("Uruguayan peso", "UYU") + .put("British pound", "GBP") + .put("Saint Helena pound", "SHP") + .put("Egyptian pound", "EGP") + .put("Falkland Islands pound", "FKP") + .put("Gibraltar pound", "GIP") + .put("Manx pound", "IMP") + .put("Jersey pound", "JEP") + .put("Lebanese pound", "LBP") + .put("South Sudanese pound", "SSP") + .put("Sudanese pound", "SDG") + .put("Syrian pound", "SYP") + .put("United States dollar", "USD") + .put("Australian dollar", "AUD") + .put("Bahamian dollar", "BSD") + .put("Barbadian dollar", "BBD") + .put("Belize dollar", "BZD") + .put("Bermudian dollar", "BMD") + .put("Brunei dollar", "BND") + .put("Singapore dollar", "SGD") + .put("Canadian dollar", "CAD") + .put("Cayman Islands dollar", "KYD") + .put("New Zealand dollar", "NZD") + .put("Fijian dollar", "FJD") + .put("Guyanese dollar", "GYD") + .put("Hong Kong dollar", "HKD") + .put("Jamaican dollar", "JMD") + .put("Liberian dollar", "LRD") + .put("Namibian dollar", "NAD") + .put("Solomon Islands dollar", "SBD") + .put("Surinamese dollar", "SRD") + .put("New Taiwan dollar", "TWD") + .put("Trinidad and Tobago dollar", "TTD") + .put("Tuvaluan dollar", "TVD") + .put("Chinese yuan", "CNY") + .put("Rial", "__RI") + .put("Shiling", "__S") + .put("Som", "__SO") + .put("Dirham", "__DR") + .put("Dinar", "_DN") + .put("Dollar", "__D") + .put("Manat", "__MA") + .put("Rupee", "__R") + .put("Krone", "__K") + .put("Krona", "__K") + .put("Crown", "__K") + .put("Frank", "__F") + .put("Mark", "__M") + .put("Ruble", "__RB") + .put("Peso", "__PE") + .put("Pound", "__P") + .put("Tristan da Cunha pound", "_TP") + .put("South Georgia and the South Sandwich Islands pound", "_SP") + .put("Somaliland shilling", "_SS") + .put("Pitcairn Islands dollar", "_PND") + .put("Palauan dollar", "_PD") + .put("Niue dollar", "_NID") + .put("Nauruan dollar", "_ND") + .put("Micronesian dollar", "_MD") + .put("Kiribati dollar", "_KID") + .put("Guernsey pound", "_GGP") + .put("Faroese króna", "_FOK") + .put("Cook Islands dollar", "_CKD") + .put("British Virgin Islands dollar", "_BD") + .put("Ascension pound", "_AP") + .put("Alderney pound", "_ALP") + .put("Abkhazian apsar", "_AA") + .build(); + + public static final ImmutableMap FractionalUnitNameToCodeMap = ImmutableMap.builder() + .put("Jiao", "JIAO") + .put("Kopek", "KOPEK") + .put("Pul", "PUL") + .put("Cent", "CENT") + .put("Qindarkë", "QINDARKE") + .put("Penny", "PENNY") + .put("Santeem", "SANTEEM") + .put("Cêntimo", "CENTIMO") + .put("Centavo", "CENTAVO") + .put("Luma", "LUMA") + .put("Qəpik", "QƏPIK") + .put("Fils", "FILS") + .put("Poisha", "POISHA") + .put("Kapyeyka", "KAPYEYKA") + .put("Centime", "CENTIME") + .put("Chetrum", "CHETRUM") + .put("Paisa", "PAISA") + .put("Fening", "FENING") + .put("Thebe", "THEBE") + .put("Sen", "SEN") + .put("Stotinka", "STOTINKA") + .put("Fen", "FEN") + .put("Céntimo", "CENTIMO") + .put("Lipa", "LIPA") + .put("Haléř", "HALER") + .put("Øre", "ØRE") + .put("Piastre", "PIASTRE") + .put("Santim", "SANTIM") + .put("Oyra", "OYRA") + .put("Butut", "BUTUT") + .put("Tetri", "TETRI") + .put("Pesewa", "PESEWA") + .put("Fillér", "FILLER") + .put("Eyrir", "EYRIR") + .put("Dinar", "DINAR") + .put("Agora", "AGORA") + .put("Tïın", "TIIN") + .put("Chon", "CHON") + .put("Jeon", "JEON") + .put("Tyiyn", "TYIYN") + .put("Att", "ATT") + .put("Sente", "SENTE") + .put("Dirham", "DIRHAM") + .put("Rappen", "RAPPEN") + .put("Avo", "AVO") + .put("Deni", "DENI") + .put("Iraimbilanja", "IRAIMBILANJA") + .put("Tambala", "TAMBALA") + .put("Laari", "LAARI") + .put("Khoums", "KHOUMS") + .put("Ban", "BAN") + .put("Möngö", "MONGO") + .put("Pya", "PYA") + .put("Kobo", "KOBO") + .put("Kuruş", "KURUS") + .put("Baisa", "BAISA") + .put("Centésimo", "CENTESIMO") + .put("Toea", "TOEA") + .put("Sentimo", "SENTIMO") + .put("Grosz", "GROSZ") + .put("Sene", "SENE") + .put("Halala", "HALALA") + .put("Para", "PARA") + .put("Öre", "ORE") + .put("Diram", "DIRAM") + .put("Satang", "SATANG") + .put("Seniti", "SENITI") + .put("Millime", "MILLIME") + .put("Tennesi", "TENNESI") + .put("Kopiyka", "KOPIYKA") + .put("Tiyin", "TIYIN") + .put("Hào", "HAO") + .put("Ngwee", "NGWEE") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?又|再)"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("United States dollar", "us$") + .put("British Virgin Islands dollar", "bvi$") + .put("Brunei dollar", "b$") + .put("Sen", "sen") + .put("Singapore dollar", "s$") + .put("Canadian dollar", "can$|c$|c $") + .put("Cayman Islands dollar", "ci$") + .put("New Zealand dollar", "nz$|nz $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hk$|hkd|hk $") + .put("Jamaican dollar", "j$") + .put("Namibian dollar", "nad|n$|n $") + .put("Solomon Islands dollar", "si$|si $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥") + .put("Turkish lira", "₺") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .build(); + + public static final List CurrencyAmbiguousValues = Arrays.asList("元", "仙", "分", "圆", "块", "毛", "盾", "箍", "蚊", "角"); + + public static final ImmutableMap DimensionSuffixList = ImmutableMap.builder() + .put("Meter", "米|公尺|m") + .put("Kilometer", "千米|公里|km") + .put("Decimeter", "分米|公寸|dm") + .put("Centimeter", "釐米|厘米|公分|cm") + .put("Micrometer", "毫米|公釐|mm") + .put("Microns", "微米") + .put("Picometer", "皮米") + .put("Nanometer", "纳米") + .put("Li", "里|市里") + .put("Zhang", "丈") + .put("Chi", "市尺|尺") + .put("Cun", "市寸|寸") + .put("Fen", "市分|分") + .put("Hao", "毫") + .put("Mile", "英里") + .put("Inch", "英寸") + .put("Foot", "呎|英尺") + .put("Yard", "码") + .put("Knot", "海里") + .put("Light year", "光年") + .put("Meter per second", "米每秒|米/秒|m/s") + .put("Kilometer per hour", "公里每小时|千米每小时|公里/小时|千米/小时|km/h") + .put("Kilometer per minute", "公里每分钟|千米每分钟|公里/分钟|千米/分钟|km/min") + .put("Kilometer per second", "公里每秒|千米每秒|公里/秒|千米/秒|km/s") + .put("Mile per hour", "英里每小时|英里/小时") + .put("Foot per second", "英尺每小时|英尺/小时") + .put("Foot per minute", "英尺每分钟|英尺/分钟") + .put("Yard per minute", "码每分|码/分") + .put("Yard per second", "码每秒|码/秒") + .put("Square centimetre", "平方厘米") + .put("Square decimeter", "平方分米") + .put("Square meter", "平方米") + .put("Square kilometer", "平方公里") + .put("Acre", "英亩|公亩") + .put("Hectare", "公顷") + .put("Mu", "亩|市亩") + .put("Liter", "公升|升|l") + .put("Milliliter", "毫升|ml") + .put("Cubic meter", "立方米") + .put("Cubic decimeter", "立方分米") + .put("Cubic millimeter", "立方毫米") + .put("Cubic feet", "立方英尺") + .put("Gallon", "加仑") + .put("Pint", "品脱") + .put("Dou", "市斗|斗") + .put("Dan", "市石|石") + .put("Kilogram", "千克|公斤|kg") + .put("Jin", "市斤|斤") + .put("Milligram", "毫克|mg") + .put("Barrel", "桶") + .put("Pot", "罐") + .put("Gram", "克|g") + .put("Ton", "公吨|吨|t") + .put("Pound", "磅") + .put("Ounce", "盎司") + .put("Liang", "两") + .put("Bit", "比特|位|b|bit") + .put("Kilobit", "千比特|千位|kb|Kb") + .put("Megabit", "兆比特|兆位|mb|Mb") + .put("Gigabit", "十亿比特|千兆比特|十亿位|千兆位|gb|Gb") + .put("Terabit", "万亿比特|兆兆比特|万亿位|兆兆位|tb|Tb") + .put("Petabit", "千兆兆比特|千万亿比特|千兆兆位|千万亿位|pb|Pb") + .put("Byte", "字节|byte|Byte") + .put("Kilobyte", "千字节|kB|KB") + .put("Megabyte", "兆字节|mB|MB") + .put("Gigabyte", "十亿字节|千兆字节|gB|GB") + .put("Terabyte", "万亿字节|兆兆字节|tB|TB") + .put("Petabyte", "千兆兆字节|千万亿字节|pB|PB") + .build(); + + public static final List DimensionAmbiguousValues = Arrays.asList("丈", "位", "克", "分", "升", "寸", "尺", "斗", "斤", "桶", "毫", "石", "码", "磅", "米", "罐", "里", "m", "km", "dm", "cm", "mm", "l", "ml", "kg", "mg", "g", "t", "b", "byte", "kb", "mb", "gb", "tb", "pb"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("五角", "五角大楼") + .put("普尔", "标准普尔") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("F", "华氏温度|华氏度|°f") + .put("K", "开尔文温度|开氏度|凯氏度|K|k") + .put("R", "兰氏温度|°r") + .put("C", "摄氏温度|摄氏度|°c") + .put("Degree", "度") + .build(); + + public static final ImmutableMap TemperaturePrefixList = ImmutableMap.builder() + .put("F", "华氏温度|华氏") + .put("K", "开氏温度|开氏") + .put("R", "兰氏温度|兰氏") + .put("C", "摄氏温度|摄氏") + .build(); + + public static final List TemperatureAmbiguousValues = Arrays.asList("度", "k"); + + public static final String HalfUnitRegex = "半"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java new file mode 100644 index 000000000..3ce10a864 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java @@ -0,0 +1,721 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishNumericWithUnit { + + public static final ImmutableMap AgePrefixList = ImmutableMap.builder() + .put("Age", "Age|age") + .build(); + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "years old|year old|year-old|years-old|-year-old|-years-old|years of age|year of age|yo") + .put("Month", "months old|month old|month-old|months-old|-month-old|-months-old|month of age|months of age|mo") + .put("Week", "weeks old|week old|week-old|weeks-old|-week-old|-weeks-old|week of age|weeks of age") + .put("Day", "days old|day old|day-old|days-old|-day-old|-days-old|day of age|days of age") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("yo", "mo"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Square kilometer", "sq km|sq kilometer|sq kilometre|sq kilometers|sq kilometres|square kilometer|square kilometre|square kilometers|square kilometres|km2|km^2|km²") + .put("Square hectometer", "sq hm|sq hectometer|sq hectometre|sq hectometers|sq hectometres|square hectometer|square hectometre|square hectometers|square hectometres|hm2|hm^2|hm²|hectare|hectares") + .put("Square decameter", "sq dam|sq decameter|sq decametre|sq decameters|sq decametres|square decameter|square decametre|square decameters|square decametres|sq dekameter|sq dekametre|sq dekameters|sq dekametres|square dekameter|square dekametre|square dekameters|square dekametres|dam2|dam^2|dam²") + .put("Square meter", "sq m|sq meter|sq metre|sq meters|sq metres|sq metre|square meter|square meters|square metre|square metres|m2|m^2|m²") + .put("Square decimeter", "sq dm|sq decimeter|sq decimetre|sq decimeters|sq decimetres|square decimeter|square decimetre|square decimeters|square decimetres|dm2|dm^2|dm²") + .put("Square centimeter", "sq cm|sq centimeter|sq centimetre|sq centimeters|sq centimetres|square centimeter|square centimetre|square centimeters|square centimetres|cm2|cm^2|cm²") + .put("Square millimeter", "sq mm|sq millimeter|sq millimetre|sq millimeters|sq millimetres|square millimeter|square millimetre|square millimeters|square millimetres|mm2|mm^2|mm²") + .put("Square inch", "sq in|sq inch|square inch|square inches|in2|in^2|in²") + .put("Square foot", "sqft|sq ft|sq foot|sq feet|square foot|square feet|feet2|feet^2|feet²|ft2|ft^2|ft²") + .put("Square mile", "sq mi|sq mile|sqmiles|square mile|square miles|mi2|mi^2|mi²") + .put("Square yard", "sq yd|sq yard|sq yards|square yard|square yards|yd2|yd^2|yd²") + .put("Acre", "-acre|acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Abkhazian apsar", "abkhazian apsar|apsars") + .put("Afghan afghani", "afghan afghani|؋|afn|afghanis|afghani") + .put("Pul", "pul") + .put("Euro", "euros|euro|€|eur") + .put("Cent", "cents|cent|-cents|-cent") + .put("Albanian lek", "albanian lek|leks|lek") + .put("Qindarkë", "qindarkë|qindarkës|qindarke|qindarkes") + .put("Angolan kwanza", "angolan kwanza|kz|aoa|kwanza|kwanzas|angolan kwanzas") + .put("Armenian dram", "armenian drams|armenian dram") + .put("Aruban florin", "aruban florin|ƒ|awg|aruban florins") + .put("Bangladeshi taka", "bangladeshi taka|৳|bdt|taka|takas|bangladeshi takas") + .put("Paisa", "poisha|paisa") + .put("Bhutanese ngultrum", "bhutanese ngultrum|nu.|btn") + .put("Chetrum", "chetrums|chetrum") + .put("Bolivian boliviano", "bolivian boliviano|bob|bs.|bolivia boliviano|bolivia bolivianos|bolivian bolivianos") + .put("Bosnia and Herzegovina convertible mark", "bosnia and herzegovina convertible mark|bam") + .put("Fening", "fenings|fenings") + .put("Botswana pula", "botswana pula|bwp|pula|pulas|botswana pulas") + .put("Thebe", "thebe") + .put("Brazilian real", "brazilian real|r$|brl|brazil real|brazil reals|brazilian reals") + .put("Bulgarian lev", "bulgarian lev|bgn|лв|bulgaria lev|bulgaria levs|bulgarian levs") + .put("Stotinka", "stotinki|stotinka") + .put("Cambodian riel", "cambodian riel|khr|៛|cambodia riel|cambodia riels|cambodian riels") + .put("Cape Verdean escudo", "cape verdean escudo|cve") + .put("Costa Rican colón", "costa rican colón|costa rican colóns|crc|₡|costa rica colón|costa rica colóns|costa rican colon|costa rican colons|costa rica colon|costa rica colons") + .put("Salvadoran colón", "svc|salvadoran colón|salvadoran colóns|salvador colón|salvador colóns|salvadoran colon|salvadoran colons|salvador colon|salvador colons") + .put("Céntimo", "céntimo") + .put("Croatian kuna", "croatian kuna|kn|hrk|croatia kuna|croatian kunas|croatian kuna kunas") + .put("Lipa", "lipa") + .put("Czech koruna", "czech koruna|czk|kč|czech korunas") + .put("Haléř", "haléř") + .put("Eritrean nakfa", "eritrean nakfa|nfk|ern|eritrean nakfas") + .put("Ethiopian birr", "ethiopian birr|etb") + .put("Gambian dalasi", "gmd") + .put("Butut", "bututs|butut") + .put("Georgian lari", "georgian lari|lari|gel|₾") + .put("Tetri", "tetri") + .put("Ghanaian cedi", "ghanaian cedi|ghs|₵|gh₵") + .put("Pesewa", "pesewas|pesewa") + .put("Guatemalan quetzal", "guatemalan quetzal|gtq|guatemala quetzal") + .put("Haitian gourde", "haitian gourde|htg") + .put("Honduran lempira", "honduran lempira|hnl") + .put("Hungarian forint", "hungarian forint|huf|ft|hungary forint|hungary forints|hungarian forints") + .put("Fillér", "fillér") + .put("Iranian rial", "iranian rial|irr|iran rial|iran rials|iranian rials") + .put("Yemeni rial", "yemeni rial|yer|yemeni rials") + .put("Israeli new shekel", "₪|ils|agora") + .put("Lithuanian litas", "ltl|lithuanian litas|lithuan litas|lithuanian lit|lithuan lit") + .put("Japanese yen", "japanese yen|jpy|yen|-yen|¥|yens|japanese yens|japan yen|japan yens") + .put("Kazakhstani tenge", "kazakhstani tenge|kazakh tenge|kazak tenge|kzt") + .put("Kenyan shilling", "kenyan shilling|kes") + .put("North Korean won", "north korean won|kpw|north korean wons") + .put("South Korean won", "south korean won|krw|south korean wons") + .put("Korean won", "korean won|₩|korean wons") + .put("Kyrgyzstani som", "kyrgyzstani som|kgs") + .put("Uzbekitan som", "uzbekitan som|uzs") + .put("Lao kip", "lao kip|lak|₭n|₭") + .put("Att", "att") + .put("Lesotho loti", "lesotho loti|lsl|loti") + .put("Sente", "sente|lisente") + .put("South African rand", "south african rand|zar|south africa rand|south africa rands|south african rands") + .put("Macanese pataca", "macanese pataca|mop$|mop") + .put("Avo", "avos|avo") + .put("Macedonian denar", "macedonian denar|mkd|ден") + .put("Deni", "deni") + .put("Malagasy ariary", "malagasy ariary|mga") + .put("Iraimbilanja", "iraimbilanja") + .put("Malawian kwacha", "malawian kwacha|mk|mwk") + .put("Tambala", "tambala") + .put("Malaysian ringgit", "malaysian ringgit|rm|myr|malaysia ringgit|malaysia ringgits|malaysian ringgits") + .put("Mauritanian ouguiya", "mauritanian ouguiya|um|mro|mauritania ouguiya|mauritania ouguiyas|mauritanian ouguiyas") + .put("Khoums", "khoums") + .put("Mongolian tögrög", "mongolian tögrög|mnt|₮|mongolia tögrög|mongolia tögrögs|mongolian tögrögs|mongolian togrog|mongolian togrogs|mongolia togrog|mongolia togrogs") + .put("Mozambican metical", "mozambican metical|mt|mzn|mozambica metical|mozambica meticals|mozambican meticals") + .put("Burmese kyat", "burmese kyat|ks|mmk") + .put("Pya", "pya") + .put("Nicaraguan córdoba", "nicaraguan córdoba|nio") + .put("Nigerian naira", "nigerian naira|naira|ngn|₦|nigeria naira|nigeria nairas|nigerian nairas") + .put("Kobo", "kobo") + .put("Turkish lira", "turkish lira|try|tl|turkey lira|turkey liras|turkish liras") + .put("Kuruş", "kuruş") + .put("Omani rial", "omani rial|omr|ر.ع.") + .put("Panamanian balboa", "panamanian balboa|b/.|pab") + .put("Centesimo", "centesimo") + .put("Papua New Guinean kina", "papua new guinean kina|kina|pgk") + .put("Toea", "toea") + .put("Paraguayan guaraní", "paraguayan guaraní|₲|pyg") + .put("Peruvian sol", "peruvian sol|soles|sol|peruvian nuevo sol") + .put("Polish złoty", "złoty|polish złoty|zł|pln|zloty|polish zloty|poland zloty|poland złoty") + .put("Grosz", "groszy|grosz|grosze") + .put("Qatari riyal", "qatari riyal|qar|qatari riyals|qatar riyal|qatar riyals") + .put("Saudi riyal", "saudi riyal|sar|saudi riyals") + .put("Riyal", "riyal|riyals|rial|﷼") + .put("Dirham", "dirham|dirhem|dirhm") + .put("Halala", "halalas|halala") + .put("Samoan tālā", "samoan tālā|tālā|tala|ws$|samoa|wst|samoan tala") + .put("Sene", "sene") + .put("São Tomé and Príncipe dobra", "são tomé and príncipe dobra|dobras|dobra") + .put("Sierra Leonean leone", "sierra leonean leone|sll|leone|le") + .put("Peseta", "pesetas|peseta") + .put("Netherlands guilder", "florin|netherlands antillean guilder|ang|nederlandse gulden|guilders|guilder|gulden|-guilders|-guilder|dutch guilders|dutch guilder|fl") + .put("Swazi lilangeni", "swazi lilangeni|lilangeni|szl|emalangeni") + .put("Tajikistani somoni", "tajikistani somoni|tjs|somoni") + .put("Diram", "dirams|diram") + .put("Thai baht", "thai baht|฿|thb|baht") + .put("Satang", "satang|satangs") + .put("Tongan paʻanga", "tongan paʻanga|paʻanga|tongan pa'anga|pa'anga") + .put("Seniti", "seniti") + .put("Ukrainian hryvnia", "ukrainian hryvnia|hyrvnia|uah|₴|ukrain hryvnia|ukrain hryvnias|ukrainian hryvnias") + .put("Vanuatu vatu", "vanuatu vatu|vatu|vuv") + .put("Venezuelan bolívar", "venezuelan bolívar|venezuelan bolívars|bs.f.|vef|bolívar fuerte|venezuelan bolivar|venezuelan bolivars|venezuela bolivar|venezuela bolivarsvenezuelan bolivar|venezuelan bolivars") + .put("Vietnamese dong", "vietnamese dong|vnd|đồng|vietnam dong|vietnamese dongs|vietnam dongs") + .put("Zambian kwacha", "zambian kwacha|zk|zmw|zambia kwacha|kwachas|zambian kwachas") + .put("Moroccan dirham", "moroccan dirham|mad|د.م.") + .put("United Arab Emirates dirham", "united arab emirates dirham|د.إ|aed") + .put("Azerbaijani manat", "azerbaijani manat|azn") + .put("Turkmenistan manat", "turkmenistan manat|turkmenistan new manat|tmt") + .put("Manat", "manats|manat") + .put("Qəpik", "qəpik") + .put("Somali shilling", "somali shillings|somali shilling|shilin soomaali|-shilin soomaali|scellino|shilin|sh.so.|sos") + .put("Somaliland shilling", "somaliland shillings|somaliland shilling|soomaaliland shilin") + .put("Tanzanian shilling", "tanzanian shilling|tanzanian shillings|tsh|tzs|tanzania shilling|tanzania shillings") + .put("Ugandan shilling", "ugandan shilling|ugandan shillings|ugx|uganda shilling|uganda shillings") + .put("Romanian leu", "romanian leu|lei|ron|romania leu") + .put("Moldovan leu", "moldovan leu|mdl|moldova leu") + .put("Leu", "leu") + .put("Ban", "bani|-ban|ban") + .put("Nepalese rupee", "nepalese rupees|nepalese rupee|npr") + .put("Pakistani rupee", "pakistani rupees|pakistani rupee|pkr") + .put("Indian rupee", "indian rupees|indian rupee|inr|₹|india rupees|india rupee") + .put("Seychellois rupee", "seychellois rupees|seychellois rupee|scr|sr|sre") + .put("Mauritian rupee", "mauritian rupees|mauritian rupee|mur") + .put("Maldivian rufiyaa", "maldivian rufiyaas|maldivian rufiyaa|mvr|.ރ|maldive rufiyaas|maldive rufiyaa") + .put("Sri Lankan rupee", "sri lankan rupees|sri lankan rupee|lkr|රු|ரூ") + .put("Indonesian rupiah", "indonesian rupiah|rupiah|perak|rp|idr") + .put("Rupee", "rupee|rupees|rs") + .put("Danish krone", "danish krone|dkk|denmark krone|denmark krones|danish krones") + .put("Norwegian krone", "norwegian krone|nok|norway krone|norway krones|norwegian krones") + .put("Faroese króna", "faroese króna|faroese krona") + .put("Icelandic króna", "icelandic króna|isk|icelandic krona|iceland króna|iceland krona") + .put("Swedish krona", "swedish krona|sek|swedan krona") + .put("Krone", "kronor|krona|króna|krone|krones|kr|-kr") + .put("Øre", "Øre|oyra|eyrir") + .put("West African CFA franc", "west african cfa franc|xof|west africa cfa franc|west africa franc|west african franc") + .put("Central African CFA franc", "central african cfa franc|xaf|central africa cfa franc|central african franc|central africa franc") + .put("Comorian franc", "comorian franc|kmf") + .put("Congolese franc", "congolese franc|cdf") + .put("Burundian franc", "burundian franc|bif") + .put("Djiboutian franc", "djiboutian franc|djf") + .put("CFP franc", "cfp franc|xpf") + .put("Guinean franc", "guinean franc|gnf") + .put("Swiss franc", "swiss francs|swiss franc|chf|sfr.") + .put("Rwandan franc", "Rwandan franc|rwf|rf|r₣|frw") + .put("Belgian franc", "belgian franc|bi.|b.fr.|bef|belgium franc") + .put("Rappen", "rappen|-rappen") + .put("Franc", "francs|franc|fr.|fs") + .put("Centime", "centimes|centime|santim") + .put("Russian ruble", "russian ruble|₽|rub|russia ruble|russia ₽|russian ₽|russian rubles|russia rubles") + .put("New Belarusian ruble", "new belarusian ruble|byn|new belarus ruble|new belarus rubles|new belarusian rubles") + .put("Old Belarusian ruble", "old belarusian ruble|byr|old belarus ruble|old belarus rubles|old belarusian rubles") + .put("Transnistrian ruble", "transnistrian ruble|prb|р.") + .put("Belarusian ruble", "belarusian ruble|belarus ruble|belarus rubles|belarusian rubles") + .put("Kopek", "kopek|kopeks") + .put("Kapyeyka", "kapyeyka") + .put("Ruble", "rubles|ruble|br") + .put("Algerian dinar", "algerian dinar|د.ج|dzd|algerian dinars|algeria dinar|algeria dinars") + .put("Bahraini dinar", "bahraini dinars|bahraini dinar|bhd|.د.ب") + .put("Santeem", "santeem|santeems") + .put("Iraqi dinar", "iraqi dinars|iraqi dinar|iraq dinars|iraq dinar|iqd|ع.د") + .put("Jordanian dinar", "jordanian dinars|jordanian dinar|د.ا|jod|jordan dinar|jordan dinars") + .put("Kuwaiti dinar", "kuwaiti dinars|kuwaiti dinar|kwd|د.ك") + .put("Libyan dinar", "libyan dinars|libyan dinar|libya dinars|libya dinar|lyd") + .put("Serbian dinar", "serbian dinars|serbian dinar|din.|rsd|дин.|serbia dinars|serbia dinar") + .put("Tunisian dinar", "tunisian dinars|tunisian dinar|tnd|tunisia dinars|tunisia dinar") + .put("Yugoslav dinar", "yugoslav dinars|yugoslav dinar|yun") + .put("Dinar", "dinars|dinar|denar|-dinars|-dinar") + .put("Fils", "fils|fulūs|-fils|-fil") + .put("Para", "para|napa") + .put("Millime", "millimes|millime") + .put("Argentine peso", "argentine peso|ars|argetina peso|argetina pesos|argentine pesos") + .put("Chilean peso", "chilean pesos|chilean peso|clp|chile peso|chile peso") + .put("Colombian peso", "colombian pesos|colombian peso|cop|colombia peso|colombia pesos") + .put("Cuban convertible peso", "cuban convertible pesos|cuban convertible peso|cuc|cuba convertible pesos|cuba convertible peso") + .put("Cuban peso", "cuban pesos|cuban peso|cup|cuba pesos|cuba peso") + .put("Dominican peso", "dominican pesos|dominican peso|dop|dominica pesos|dominica peso") + .put("Mexican peso", "mexican pesos|mexican peso|mxn|mexico pesos|mexico peso") + .put("Philippine peso", "piso|philippine pesos|philippine peso|₱|php") + .put("Uruguayan peso", "uruguayan pesos|uruguayan peso|uyu") + .put("Peso", "pesos|peso") + .put("Centavo", "centavos|centavo") + .put("Alderney pound", "alderney pounds|alderney pound|alderney £") + .put("British pound", "british pounds|british pound|british £|gbp|pound sterling|pound sterlings|sterling|pound scot|pound scots") + .put("Guernsey pound", "guernsey pounds|guernsey £|ggp") + .put("Ascension pound", "ascension pounds|ascension pound|ascension £") + .put("Saint Helena pound", "saint helena pounds|saint helena pound|saint helena £|shp") + .put("Egyptian pound", "egyptian pounds|egyptian pound|egyptian £|egp|ج.م|egypt pounds|egypt pound") + .put("Falkland Islands pound", "falkland islands pounds|falkland islands pound|falkland islands £|fkp|falkland island pounds|falkland island pound|falkland island £") + .put("Gibraltar pound", "gibraltar pounds|gibraltar pound|gibraltar £|gip") + .put("Manx pound", "manx pounds|manx pound|manx £|imp") + .put("Jersey pound", "jersey pounds|jersey pound|jersey £|jep") + .put("Lebanese pound", "lebanese pounds|lebanese pound|lebanese £|lebanan pounds|lebanan pound|lebanan £|lbp|ل.ل") + .put("South Georgia and the South Sandwich Islands pound", "south georgia and the south sandwich islands pounds|south georgia and the south sandwich islands pound|south georgia and the south sandwich islands £") + .put("South Sudanese pound", "south sudanese pounds|south sudanese pound|south sudanese £|ssp|south sudan pounds|south sudan pound|south sudan £") + .put("Sudanese pound", "sudanese pounds|sudanese pound|sudanese £|ج.س.|sdg|sudan pounds|sudan pound|sudan £") + .put("Syrian pound", "syrian pounds|syrian pound|syrian £|ل.س|syp|syria pounds|syria pound|syria £") + .put("Tristan da Cunha pound", "tristan da cunha pounds|tristan da cunha pound|tristan da cunha £") + .put("Pound", "pounds|pound|-pounds|-pound|£") + .put("Pence", "pence") + .put("Shilling", "shillings|shilling|shilingi|sh") + .put("Penny", "pennies|penny") + .put("United States dollar", "united states dollars|united states dollar|united states $|u.s. dollars|u.s. dollar|u s dollar|u s dollars|usd|american dollars|american dollar|us$|us dollar|us dollars|u.s dollar|u.s dollars") + .put("East Caribbean dollar", "east caribbean dollars|east caribbean dollar|east Caribbean $|xcd") + .put("Australian dollar", "australian dollars|australian dollar|australian $|australian$|aud|australia dollars|australia dollar|australia $|australia$") + .put("Bahamian dollar", "bahamian dollars|bahamian dollar|bahamian $|bahamian$|bsd|bahamia dollars|bahamia dollar|bahamia $|bahamia$") + .put("Barbadian dollar", "barbadian dollars|barbadian dollar|barbadian $|bbd") + .put("Belize dollar", "belize dollars|belize dollar|belize $|bzd") + .put("Bermudian dollar", "bermudian dollars|bermudian dollar|bermudian $|bmd|bermudia dollars|bermudia dollar|bermudia $") + .put("British Virgin Islands dollar", "british virgin islands dollars|british virgin islands dollar|british virgin islands $|bvi$|virgin islands dollars|virgin islands dolalr|virgin islands $|virgin island dollars|virgin island dollar|virgin island $") + .put("Brunei dollar", "brunei dollar|brunei $|bnd") + .put("Sen", "sen") + .put("Singapore dollar", "singapore dollars|singapore dollar|singapore $|s$|sgd") + .put("Canadian dollar", "canadian dollars|canadian dollar|canadian $|cad|can$|c$|canada dollars|canada dolllar|canada $") + .put("Cayman Islands dollar", "cayman islands dollars|cayman islands dollar|cayman islands $|kyd|ci$|cayman island dollar|cayman island doolars|cayman island $") + .put("New Zealand dollar", "new zealand dollars|new zealand dollar|new zealand $|nz$|nzd|kiwi") + .put("Cook Islands dollar", "cook islands dollars|cook islands dollar|cook islands $|cook island dollars|cook island dollar|cook island $") + .put("Fijian dollar", "fijian dollars|fijian dollar|fijian $|fjd|fiji dollars|fiji dollar|fiji $") + .put("Guyanese dollar", "guyanese dollars|guyanese dollar|gyd|gy$") + .put("Hong Kong dollar", "hong kong dollars|hong kong dollar|hong kong $|hk$|hkd|hk dollars|hk dollar|hk $|hongkong$") + .put("Jamaican dollar", "jamaican dollars|jamaican dollar|jamaican $|j$|jamaica dollars|jamaica dollar|jamaica $|jmd") + .put("Kiribati dollar", "kiribati dollars|kiribati dollar|kiribati $") + .put("Liberian dollar", "liberian dollars|liberian dollar|liberian $|liberia dollars|liberia dollar|liberia $|lrd") + .put("Micronesian dollar", "micronesian dollars|micronesian dollar|micronesian $") + .put("Namibian dollar", "namibian dollars|namibian dollar|namibian $|nad|n$|namibia dollars|namibia dollar|namibia $") + .put("Nauruan dollar", "nauruan dollars|nauruan dollar|nauruan $") + .put("Niue dollar", "niue dollars|niue dollar|niue $") + .put("Palauan dollar", "palauan dollars|palauan dollar|palauan $") + .put("Pitcairn Islands dollar", "pitcairn islands dollars|pitcairn islands dollar|pitcairn islands $|pitcairn island dollars|pitcairn island dollar|pitcairn island $") + .put("Solomon Islands dollar", "solomon islands dollars|solomon islands dollar|solomon islands $|si$|sbd|solomon island dollars|solomon island dollar|solomon island $") + .put("Surinamese dollar", "surinamese dollars|surinamese dollar|surinamese $|srd") + .put("New Taiwan dollar", "new taiwan dollars|new taiwan dollar|nt$|twd|ntd") + .put("Trinidad and Tobago dollar", "trinidad and tobago dollars|trinidad and tobago dollar|trinidad and tobago $|trinidad $|trinidad dollar|trinidad dollars|trinidadian dollar|trinidadian dollars|trinidadian $|ttd") + .put("Tuvaluan dollar", "tuvaluan dollars|tuvaluan dollar|tuvaluan $") + .put("Dollar", "dollars|dollar|$") + .put("Chinese yuan", "yuan|kuai|chinese yuan|renminbi|cny|rmb|¥|元") + .put("Fen", "fen") + .put("Jiao", "jiao|mao") + .put("Finnish markka", "suomen markka|finnish markka|finsk mark|fim|markkaa|markka") + .put("Penni", "penniä|penni") + .build(); + + public static final ImmutableMap CurrencyNameToIsoCodeMap = ImmutableMap.builder() + .put("Afghan afghani", "AFN") + .put("Euro", "EUR") + .put("Albanian lek", "ALL") + .put("Angolan kwanza", "AOA") + .put("Armenian dram", "AMD") + .put("Aruban florin", "AWG") + .put("Bangladeshi taka", "BDT") + .put("Bhutanese ngultrum", "BTN") + .put("Bolivian boliviano", "BOB") + .put("Bosnia and Herzegovina convertible mark", "BAM") + .put("Botswana pula", "BWP") + .put("Brazilian real", "BRL") + .put("Bulgarian lev", "BGN") + .put("Cambodian riel", "KHR") + .put("Cape Verdean escudo", "CVE") + .put("Costa Rican colón", "CRC") + .put("Croatian kuna", "HRK") + .put("Czech koruna", "CZK") + .put("Eritrean nakfa", "ERN") + .put("Ethiopian birr", "ETB") + .put("Gambian dalasi", "GMD") + .put("Georgian lari", "GEL") + .put("Ghanaian cedi", "GHS") + .put("Guatemalan quetzal", "GTQ") + .put("Haitian gourde", "HTG") + .put("Honduran lempira", "HNL") + .put("Hungarian forint", "HUF") + .put("Iranian rial", "IRR") + .put("Yemeni rial", "YER") + .put("Israeli new shekel", "ILS") + .put("Japanese yen", "JPY") + .put("Kazakhstani tenge", "KZT") + .put("Kenyan shilling", "KES") + .put("North Korean won", "KPW") + .put("South Korean won", "KRW") + .put("Kyrgyzstani som", "KGS") + .put("Lao kip", "LAK") + .put("Lesotho loti", "LSL") + .put("South African rand", "ZAR") + .put("Macanese pataca", "MOP") + .put("Macedonian denar", "MKD") + .put("Malagasy ariary", "MGA") + .put("Malawian kwacha", "MWK") + .put("Malaysian ringgit", "MYR") + .put("Mauritanian ouguiya", "MRO") + .put("Mongolian tögrög", "MNT") + .put("Mozambican metical", "MZN") + .put("Burmese kyat", "MMK") + .put("Nicaraguan córdoba", "NIO") + .put("Nigerian naira", "NGN") + .put("Turkish lira", "TRY") + .put("Omani rial", "OMR") + .put("Panamanian balboa", "PAB") + .put("Papua New Guinean kina", "PGK") + .put("Paraguayan guaraní", "PYG") + .put("Peruvian sol", "PEN") + .put("Polish złoty", "PLN") + .put("Qatari riyal", "QAR") + .put("Saudi riyal", "SAR") + .put("Samoan tālā", "WST") + .put("São Tomé and Príncipe dobra", "STN") + .put("Sierra Leonean leone", "SLL") + .put("Swazi lilangeni", "SZL") + .put("Tajikistani somoni", "TJS") + .put("Thai baht", "THB") + .put("Ukrainian hryvnia", "UAH") + .put("Vanuatu vatu", "VUV") + .put("Venezuelan bolívar", "VEF") + .put("Zambian kwacha", "ZMW") + .put("Moroccan dirham", "MAD") + .put("United Arab Emirates dirham", "AED") + .put("Azerbaijani manat", "AZN") + .put("Turkmenistan manat", "TMT") + .put("Somali shilling", "SOS") + .put("Tanzanian shilling", "TZS") + .put("Ugandan shilling", "UGX") + .put("Romanian leu", "RON") + .put("Moldovan leu", "MDL") + .put("Nepalese rupee", "NPR") + .put("Pakistani rupee", "PKR") + .put("Indian rupee", "INR") + .put("Seychellois rupee", "SCR") + .put("Mauritian rupee", "MUR") + .put("Maldivian rufiyaa", "MVR") + .put("Sri Lankan rupee", "LKR") + .put("Indonesian rupiah", "IDR") + .put("Danish krone", "DKK") + .put("Norwegian krone", "NOK") + .put("Icelandic króna", "ISK") + .put("Swedish krona", "SEK") + .put("West African CFA franc", "XOF") + .put("Central African CFA franc", "XAF") + .put("Comorian franc", "KMF") + .put("Congolese franc", "CDF") + .put("Burundian franc", "BIF") + .put("Djiboutian franc", "DJF") + .put("CFP franc", "XPF") + .put("Guinean franc", "GNF") + .put("Swiss franc", "CHF") + .put("Rwandan franc", "RWF") + .put("Russian ruble", "RUB") + .put("Transnistrian ruble", "PRB") + .put("New Belarusian ruble", "BYN") + .put("Algerian dinar", "DZD") + .put("Bahraini dinar", "BHD") + .put("Iraqi dinar", "IQD") + .put("Jordanian dinar", "JOD") + .put("Kuwaiti dinar", "KWD") + .put("Libyan dinar", "LYD") + .put("Serbian dinar", "RSD") + .put("Tunisian dinar", "TND") + .put("Argentine peso", "ARS") + .put("Chilean peso", "CLP") + .put("Colombian peso", "COP") + .put("Cuban convertible peso", "CUC") + .put("Cuban peso", "CUP") + .put("Dominican peso", "DOP") + .put("Mexican peso", "MXN") + .put("Uruguayan peso", "UYU") + .put("British pound", "GBP") + .put("Saint Helena pound", "SHP") + .put("Egyptian pound", "EGP") + .put("Falkland Islands pound", "FKP") + .put("Gibraltar pound", "GIP") + .put("Manx pound", "IMP") + .put("Jersey pound", "JEP") + .put("Lebanese pound", "LBP") + .put("South Sudanese pound", "SSP") + .put("Sudanese pound", "SDG") + .put("Syrian pound", "SYP") + .put("United States dollar", "USD") + .put("Australian dollar", "AUD") + .put("Bahamian dollar", "BSD") + .put("Barbadian dollar", "BBD") + .put("Belize dollar", "BZD") + .put("Bermudian dollar", "BMD") + .put("Brunei dollar", "BND") + .put("Singapore dollar", "SGD") + .put("Canadian dollar", "CAD") + .put("Cayman Islands dollar", "KYD") + .put("New Zealand dollar", "NZD") + .put("Fijian dollar", "FJD") + .put("Guyanese dollar", "GYD") + .put("Hong Kong dollar", "HKD") + .put("Jamaican dollar", "JMD") + .put("Liberian dollar", "LRD") + .put("Namibian dollar", "NAD") + .put("Solomon Islands dollar", "SBD") + .put("Surinamese dollar", "SRD") + .put("New Taiwan dollar", "TWD") + .put("Trinidad and Tobago dollar", "TTD") + .put("Tuvaluan dollar", "TVD") + .put("Chinese yuan", "CNY") + .put("Rial", "__RI") + .put("Shiling", "__S") + .put("Som", "__SO") + .put("Dirham", "__DR") + .put("Dinar", "_DN") + .put("Dollar", "__D") + .put("Manat", "__MA") + .put("Rupee", "__R") + .put("Krone", "__K") + .put("Krona", "__K") + .put("Crown", "__K") + .put("Frank", "__F") + .put("Mark", "__M") + .put("Ruble", "__RB") + .put("Peso", "__PE") + .put("Pound", "__P") + .put("Tristan da Cunha pound", "_TP") + .put("South Georgia and the South Sandwich Islands pound", "_SP") + .put("Somaliland shilling", "_SS") + .put("Pitcairn Islands dollar", "_PND") + .put("Palauan dollar", "_PD") + .put("Niue dollar", "_NID") + .put("Nauruan dollar", "_ND") + .put("Micronesian dollar", "_MD") + .put("Kiribati dollar", "_KID") + .put("Guernsey pound", "_GGP") + .put("Faroese króna", "_FOK") + .put("Cook Islands dollar", "_CKD") + .put("British Virgin Islands dollar", "_BD") + .put("Ascension pound", "_AP") + .put("Alderney pound", "_ALP") + .put("Abkhazian apsar", "_AA") + .build(); + + public static final ImmutableMap FractionalUnitNameToCodeMap = ImmutableMap.builder() + .put("Jiao", "JIAO") + .put("Kopek", "KOPEK") + .put("Pul", "PUL") + .put("Cent", "CENT") + .put("Qindarkë", "QINDARKE") + .put("Penny", "PENNY") + .put("Santeem", "SANTEEM") + .put("Cêntimo", "CENTIMO") + .put("Centavo", "CENTAVO") + .put("Luma", "LUMA") + .put("Qəpik", "QƏPIK") + .put("Fils", "FILS") + .put("Poisha", "POISHA") + .put("Kapyeyka", "KAPYEYKA") + .put("Centime", "CENTIME") + .put("Chetrum", "CHETRUM") + .put("Paisa", "PAISA") + .put("Fening", "FENING") + .put("Thebe", "THEBE") + .put("Sen", "SEN") + .put("Stotinka", "STOTINKA") + .put("Fen", "FEN") + .put("Céntimo", "CENTIMO") + .put("Lipa", "LIPA") + .put("Haléř", "HALER") + .put("Øre", "ØRE") + .put("Piastre", "PIASTRE") + .put("Santim", "SANTIM") + .put("Oyra", "OYRA") + .put("Butut", "BUTUT") + .put("Tetri", "TETRI") + .put("Pesewa", "PESEWA") + .put("Fillér", "FILLER") + .put("Eyrir", "EYRIR") + .put("Dinar", "DINAR") + .put("Agora", "AGORA") + .put("Tïın", "TIIN") + .put("Chon", "CHON") + .put("Jeon", "JEON") + .put("Tyiyn", "TYIYN") + .put("Att", "ATT") + .put("Sente", "SENTE") + .put("Dirham", "DIRHAM") + .put("Rappen", "RAPPEN") + .put("Avo", "AVO") + .put("Deni", "DENI") + .put("Iraimbilanja", "IRAIMBILANJA") + .put("Tambala", "TAMBALA") + .put("Laari", "LAARI") + .put("Khoums", "KHOUMS") + .put("Ban", "BAN") + .put("Möngö", "MONGO") + .put("Pya", "PYA") + .put("Kobo", "KOBO") + .put("Kuruş", "KURUS") + .put("Baisa", "BAISA") + .put("Centésimo", "CENTESIMO") + .put("Toea", "TOEA") + .put("Sentimo", "SENTIMO") + .put("Grosz", "GROSZ") + .put("Sene", "SENE") + .put("Halala", "HALALA") + .put("Para", "PARA") + .put("Öre", "ORE") + .put("Diram", "DIRAM") + .put("Satang", "SATANG") + .put("Seniti", "SENITI") + .put("Millime", "MILLIME") + .put("Tennesi", "TENNESI") + .put("Kopiyka", "KOPIYKA") + .put("Tiyin", "TIYIN") + .put("Hào", "HAO") + .put("Ngwee", "NGWEE") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?and)"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dobra", "db|std") + .put("Dollar", "$") + .put("Brazilian Real", "R$") + .put("United States dollar", "united states $|us$|us $|u.s. $|u.s $") + .put("East Caribbean dollar", "east caribbean $") + .put("Australian dollar", "australian $|australia $") + .put("Bahamian dollar", "bahamian $|bahamia $") + .put("Barbadian dollar", "barbadian $|barbadin $") + .put("Belize dollar", "belize $") + .put("Bermudian dollar", "bermudian $") + .put("British Virgin Islands dollar", "british virgin islands $|bvi$|virgin islands $|virgin island $|british virgin island $") + .put("Brunei dollar", "brunei $|b$") + .put("Sen", "sen") + .put("Singapore dollar", "singapore $|s$") + .put("Canadian dollar", "canadian $|can$|c$|c $|canada $") + .put("Cayman Islands dollar", "cayman islands $|ci$|cayman island $") + .put("New Zealand dollar", "new zealand $|nz$|nz $") + .put("Cook Islands dollar", "cook islands $|cook island $") + .put("Fijian dollar", "fijian $|fiji $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hong kong $|hk$|hkd|hk $") + .put("Indian rupee", "₹") + .put("Jamaican dollar", "jamaican $|j$|jamaica $") + .put("Kiribati dollar", "kiribati $") + .put("Liberian dollar", "liberian $|liberia $") + .put("Micronesian dollar", "micronesian $") + .put("Namibian dollar", "namibian $|nad|n$|namibia $") + .put("Nauruan dollar", "nauruan $") + .put("Niue dollar", "niue $") + .put("Palauan dollar", "palauan $") + .put("Pitcairn Islands dollar", "pitcairn islands $|pitcairn island $") + .put("Solomon Islands dollar", "solomon islands $|si$|si $|solomon island $") + .put("Surinamese dollar", "surinamese $|surinam $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Trinidad and Tobago dollar", "trinidad and tobago $|trinidad $|trinidadian $") + .put("Tuvaluan dollar", "tuvaluan $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .put("Turkish lira", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("din.", "kiwi", "kina", "kobo", "lari", "lipa", "napa", "para", "sfr.", "taka", "tala", "toea", "vatu", "yuan", "all", "ang", "ban", "bob", "btn", "byr", "cad", "cop", "cup", "dop", "gip", "jod", "kgs", "lak", "lei", "mga", "mop", "nad", "omr", "pul", "sar", "sbd", "scr", "sdg", "sek", "sen", "sol", "sos", "std", "try", "yer", "yen", "db"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("Bit", "-bit|bit|bits") + .put("Kilobit", "kilobit|kilobits|kb|Kb|kbit") + .put("Megabit", "megabit|megabits|mb|Mb|mbit") + .put("Gigabit", "gigabit|gigabits|gb|Gb|gbit") + .put("Terabit", "terabit|terabits|tb|Tb|tbit") + .put("Petabit", "petabit|petabits|pb|Pb|pbit") + .put("Byte", "-byte|byte|bytes") + .put("Kilobyte", "-kilobyte|-kilobytes|kilobyte|kB|KB|kilobytes|kilo byte|kilo bytes|kbyte") + .put("Megabyte", "-megabyte|-megabytes|megabyte|mB|MB|megabytes|mega byte|mega bytes|mbyte") + .put("Gigabyte", "-gigabyte|-gigabytes|gigabyte|gB|GB|gigabytes|giga byte|giga bytes|gbyte") + .put("Terabyte", "-terabyte|-terabytes|terabyte|tB|TB|terabytes|tera byte|tera bytes|tbyte") + .put("Petabyte", "-petabyte|-petabytes|petabyte|pB|PB|petabytes|peta byte|peta bytes|pbyte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("barrel", "barrels", "grain", "pound", "stone", "yards", "yard", "cord", "dram", "feet", "foot", "gill", "knot", "peck", "cup", "fps", "pts", "in", "dm", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^))"; + + public static final String BuildSuffix = "(?=(\\s|\\W|$))"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilometer", "km|kilometer|kilometre|kilometers|kilometres|kilo meter|kilo meters|kilo metres|kilo metre") + .put("Hectometer", "hm|hectometer|hectometre|hectometers|hectometres|hecto meter|hecto meters|hecto metres|hecto metre") + .put("Decameter", "dam|decameter|decametre|decameters|decametres|deca meter|deca meters|deca metres|deca metre") + .put("Meter", "m|meter|metre|meters|metres") + .put("Decimeter", "dm|decimeter|decimeters|decimetre|decimetres|deci meter|deci meters|deci metres|deci metre") + .put("Centimeter", "cm|centimeter|centimeters|centimetre|centimetres|centi meter|centi meters|centi metres|centi metre") + .put("Millimeter", "mm|millimeter|millimeters|millimetre|millimetres|milli meter|milli meters|milli metres|milli metre") + .put("Micrometer", "μm|micrometer|micrometre|micrometers|micrometres|micro meter|micro meters|micro metres|micro metre") + .put("Nanometer", "nm|nanometer|nanometre|nanometers|nanometres|nano meter|nano meters|nano metres|nano metre") + .put("Picometer", "pm|picometer|picometre|picometers|picometres|pico meter|pico meters|pico metres|pico metre") + .put("Mile", "-mile|mile|miles") + .put("Yard", "yard|yards") + .put("Inch", "-inch|inch|inches|in|\"") + .put("Foot", "-foot|foot|feet|ft") + .put("Light year", "light year|light-year|light years|light-years") + .put("Pt", "pt|pts") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("m", "yard", "yards", "pm", "pt", "pts"); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Meter per second", "meters / second|m/s|meters per second|metres per second|meter per second|metre per second") + .put("Kilometer per hour", "km/h|kilometres per hour|kilometers per hour|kilometer per hour|kilometre per hour") + .put("Kilometer per minute", "km/min|kilometers per minute|kilometres per minute|kilometer per minute|kilometre per minute") + .put("Kilometer per second", "km/s|kilometers per second|kilometres per second|kilometer per second|kilometre per second") + .put("Mile per hour", "mph|mile per hour|miles per hour|mi/h|mile / hour|miles / hour|miles an hour") + .put("Knot", "kt|knot|kn") + .put("Foot per second", "ft/s|foot/s|foot per second|feet per second|fps") + .put("Foot per minute", "ft/min|foot/min|foot per minute|feet per minute") + .put("Yard per minute", "yards per minute|yard per minute|yards / minute|yards/min|yard/min") + .put("Yard per second", "yards per second|yard per second|yards / second|yards/s|yard/s") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("F", "degrees fahrenheit|degree fahrenheit|deg fahrenheit|degs fahrenheit|fahrenheit|°f|degrees farenheit|degree farenheit|deg farenheit|degs farenheit|degrees f|degree f|deg f|degs f|farenheit|f") + .put("K", "k|K|kelvin") + .put("R", "rankine|°r") + .put("D", "delisle|°de") + .put("C", "degrees celsius|degree celsius|deg celsius|degs celsius|celsius|degrees celcius|degree celcius|celcius|deg celcius|degs celcius|degrees centigrade|degree centigrade|centigrade|degrees centigrate|degree centigrate|degs centigrate|deg centigrate|centigrate|degrees c|degree c|deg c|degs c|°c|c") + .put("Degree", "degree|degrees|deg.|deg|°") + .build(); + + public static final List AmbiguousTemperatureUnitList = Arrays.asList("c", "f", "k"); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Cubic meter", "m3|cubic meter|cubic meters|cubic metre|cubic metres") + .put("Cubic centimeter", "cubic centimeter|cubic centimetre|cubic centimeters|cubic centimetres") + .put("Cubic millimiter", "cubic millimiter|cubic millimitre|cubic millimiters|cubic millimitres") + .put("Hectoliter", "hectoliter|hectolitre|hectoliters|hectolitres") + .put("Decaliter", "decaliter|decalitre|dekaliter|dekalitre|decaliters|decalitres|dekaliters|dekalitres") + .put("Liter", "l|litre|liter|liters|litres") + .put("Deciliter", "dl|deciliter|decilitre|deciliters|decilitres") + .put("Centiliter", "cl|centiliter|centilitre|centiliters|centilitres") + .put("Milliliter", "ml|mls|millilitre|milliliter|millilitres|milliliters") + .put("Cubic yard", "cubic yard|cubic yards") + .put("Cubic inch", "cubic inch|cubic inches") + .put("Cubic foot", "cubic foot|cubic feet") + .put("Cubic mile", "cubic mile|cubic miles") + .put("Fluid ounce", "fl oz|fluid ounce|fluid ounces") + .put("Teaspoon", "teaspoon|teaspoons") + .put("Tablespoon", "tablespoon|tablespoons") + .put("Pint", "pint|pints") + .put("Volume unit", "fluid dram|gill|quart|minim|cord|peck|bushel|hogshead|barrels|barrel|bbl") + .build(); + + public static final List AmbiguousVolumeUnitList = Arrays.asList("l", "ounce", "oz", "cup", "peck", "cord", "gill"); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Kilogram", "kg|kilogram|kilograms|kilo|kilos") + .put("Gram", "g|gram|grams") + .put("Milligram", "mg|milligram|milligrams") + .put("Gallon", "-gallon|gallons|gallon") + .put("Metric ton", "metric tons|metric ton") + .put("Ton", "-ton|ton|tons|tonne|tonnes") + .put("Pound", "pound|pounds|lb|lbs") + .put("Ounce", "-ounce|ounce|oz|ounces") + .put("Weight unit", "pennyweight|grain|british long ton|us short hundredweight|stone|dram") + .build(); + + public static final List AmbiguousWeightUnitList = Arrays.asList("g", "oz", "stone", "dram", "lbs"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("\\bm\\b", "((('|’)\\s*m)|(m\\s*('|’)))") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java new file mode 100644 index 000000000..56040b03c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java @@ -0,0 +1,401 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class FrenchNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Ans", "ans") + .put("Mois", "mois d'âge|mois d'age|mois") + .put("Semaines", "semaine|semaines|semaines d'âge|semaines d'age") + .put("Jour", "jours|jour") + .build(); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Kilomètre carré", "km2|km^2|km²|kilomètres carrés|kilomètre carré") + .put("Hectomètre carré", "hm2|hm^2|hm²|hectomètre carré|hectomètres carrés") + .put("Décamètre carré", "dam2|dam^2|dam²|décamètre carré|décamètres carrés") + .put("Mètre carré", "m2|m^2|m²|mètre carré|mètres carrés") + .put("Décimètre carré", "dm2|dm^2|dm²|décimètre carré|décimètres carrés") + .put("Centimètre carré", "cm2|cm^2|cm²|centimètre carré|centimètres carrés") + .put("Millimètre carré", "mm2|mm^2|mm²|millimètre carré|millimètres carrés") + .put("Pouce carré", "pouces2|po2|pouce carré|pouces carrés|in^2|in²|in2") + .put("Pied carré", "pied carré|pieds carrés|pi2|pi^2|pi²") + .put("Mile carré", "mile carré|miles carrés|mi2|mi^2|mi²") + .put("Acre", "acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Abkhazie apsar", "abkhazie apsar|apsars") + .put("Afghan afghani", "afghan afghani|؋|afn|afghanis|afghani") + .put("Pul", "pul") + .put("Euro", "euros|euro|€|eur|d'euros") + .put("Cent", "cents|cent|-cents|-cent") + .put("lek Albanais", "lek albanais|leks|lek") + .put("Qindarkë", "qindarkë|qindarkës|qindarke|qindarkes") + .put("Kwanza angolais", "kwanza angolais|kz|aoa|kwanza|kwanzas") + .put("Dram arménien", "dram arménien|drams arméniens") + .put("Florins d'Aruba", "florins aruba|ƒ|awg") + .put("Bangladeshi taka", "bangladeshi taka|৳|bdt|taka|takas|bangladeshi takas") + .put("Paisa", "poisha|paisa") + .put("Ngultrum bhoutanais", "ngultrum bhoutanais|nu.|btn") + .put("Chetrum", "chetrums|chetrum") + .put("Boliviano bolivien", "boliviano bolivien|bolivianos bolivien|bolivianos bolivie|boliviano bolivie|bob|bs.") + .put("Bosnie-Herzégovine mark convertible", "bosnie-herzégovine mark convertible|bosnie-et-herzégovine mark convertible|bam") + .put("Fening", "fening|fenings") + .put("Pula", "pula|bwp") + .put("Thebe", "thebe") + .put("Réal brésilien", "réal brésilien|réals brésilien|r$|brl|real bresil|reals bresilien") + .put("Lev bulgare", "lev bulgare|levs bulgare|lv|bgn") + .put("Stotinki búlgaro", "stotinki bulgare") + .put("Riel cambodgien", "riel cambodgien|khr|៛") + .put("Escudo du cap-vert", "escudo cap-verdien|cve") + .put("Colon du costa rica", "colon du costa rica|colons du costa rica|crc|₡") + .put("Colon du salvador", "colon du salvador|colons du salvador|svc") + .put("Kuna croate", "kuna croate|kunas croate|kn|hrk") + .put("Lipa", "lipa") + .put("Couronne tchèque", "couronne tchèque|couronnes tchèque|czk|Kč") + .put("Haléř", "haléř") + .put("Nakfas érythréens", "nakfas érythréens|nfk|ern|nakfa érythréens") + .put("Birr éthiopien", "birr éthiopien|birrs éthiopien|etb") + .put("Dalasi gambienne", "gmd") + .put("Butut", "bututs|butut") + .put("Lari géorgien", "lari géorgie|lari géorgiens|gel|₾") + .put("Tetri géorgien", "tetri géorgie|tetris géorgiens") + .put("Cedi", "cedi|ghs|cedi ghanéen|gh₵") + .put("Pesewa", "pesewa|pesewas") + .put("Quetzal guatémaltèque", "quetzal guatémaltèque|gtq|quetzal|quetzales") + .put("Gourdes haïtiennes", "gourdes haïtiennes|gourdes|htg|gourde haïtienne") + .put("Lempira hondurien", "lempira hondurien|hnl") + .put("Forint hongrois", "forint hongrois|huf|fg|forints hongrois") + .put("Fillér", "fillér") + .put("Rial iranien", "rial iranien|irr|rials iranien|rials iraniens") + .put("Litas lituanien", "litas lituanien|ltl|lit lithuanien|litas lithuanie") + .put("Yen Japonais", "yen japonais|yen japon|yens|jpy|yen|¥|-yen") + .put("Tenge kazakh", "tenge kazakh|kzt") + .put("Shilling kényan", "shilling kényan|kes|shillings kényans") + .put("Won coréen", "won coréen|won coréens|₩") + .put("Won sud-coréen", "won sud-coréen|won sud coréen|won sud-coréens|krw") + .put("Corée du nord won", "corée du nord won|corée nord won|kpw") + .put("Som Kirghizie", "som kirghizie|kgs") + .put("Sum Ouzbékistan", "sum ouzbékistan|sum ouzbeks|sum ouzbéks|uzs") + .put("Kip laotien", "kip laotien|lak|₭n|₭") + .put("Att", "att") + .put("Loti", "loti|maloti|lsl") + .put("Sente", "sente|lisente") + .put("Rand sud-africain", "rand sud-africain|zar") + .put("Pataca macanais", "pataca macanais|mop$|mop") + .put("Avo", "avos|avo") + .put("Dinar macédonien", "dinar macédonien|mkd|ден") + .put("Deni", "deni") + .put("Ariary malagache", "ariary malagache|mga") + .put("Iraimbilanja", "Iraimbilanja") + .put("Kwacha malawien", "kwacha malawien|mk|mwk") + .put("Tambala", "Tambala") + .put("Ringitt malaisien", "ringitt malaisien|rm|myr|ringitts malaisien") + .put("Ouguiya mauritanienne", "ouguiya|um|mro|ouguiya mauritanien|ouguiya mauritanienne") + .put("Khoums", "khoums") + .put("Togrogs mongoles", "togrogs mongoles|togrogs|tugriks|tögrög|mnt|₮|tögrög mongoles|tögrög mongolie|togrogs mongolie") + .put("Metical mozambique", "metical du mozambique|metical mozambique|mt|mzn|meticals mozambique") + .put("Kyat birmanie", "kyat birmanie|ks|mmk") + .put("Pya", "pya") + .put("Cordoba nicaraguayen", "cordoba nicaraguayen|córdoba nicaraguayen|nio|córdoba oro|cordoba oro nicaraguayen") + .put("Naira nigérians", "naira nigérians|naira|ngm|₦|nairas nigérians") + .put("Livre turque", "livre turque|try|tl|livre turques") + .put("Kuruş", "kuruş") + .put("Rials omanais", "rials omanais|omr|ر.ع.|rial omanais") + .put("Balboa panaméennes", "balboa panaméennes|balboa|pab") + .put("Kina", "kina|pkg|pgk") + .put("Toea", "toea") + .put("Guaraní paraguayen", "guaraní paraguayen|₲|pyg") + .put("Sol péruvien", "nuevo sol péruvien|soles|sol|sol péruvien") + .put("Złoty polonais", "złoty polonais|złoty|zł|pln|zloty|zloty polonais") + .put("Groxz", "groszy|grosz|grosze") + .put("Riyal qatari", "riyal qatari|qar|riyals qatari") + .put("Riyal saudi", "riyal saudi|sar|riyals saudi") + .put("Riyal", "riyal|riyals|rial|﷼") + .put("Dirham", "dirham|dirhem|dirhm") + .put("Halala", "halalas|halala") + .put("Tala", "tala|tālā|ws$|sat|wst") + .put("Sene", "sene") + .put("Dobra", "dobra|db|std") + .put("Leone", "leone|sll") + .put("Florins Néerlandais", "florins hollandais|florins néerlandais|florins|ang|florin|fl") + .put("Lilangeni", "lilangeni|szl") + .put("Somoni tadjikistan", "somoni tadjikistan|tjs|somoni") + .put("Diram", "dirams|diram") + .put("Baht thaïlandais", "baht thaïlandais|baht thailandais|baht thaï|baht thai|baht|฿|thb") + .put("Satang", "satang|satangs") + .put("Paʻanga", "paʻanga|pa'anga|top") + .put("Hryvnia ukrainien", "hryvnia ukrainien|hyrvnia|uah|₴|hryvnias ukrainien|hryvnia ukrainienne") + .put("Vanuatu vatu", "vanuatu vatu|vatu|vuv") + .put("Bolívar vénézuélien", "bolívar vénézuélien|bolivar venezuelien|bs.f.|vef|bolívars vénézuélien|bolivars venezuelien") + .put("Dong vietnamien", "dong vietnamien|dongs vietnamiens|dong|đồng|vnd|dông|dông vietnamiens") + .put("Kwacha de Zambie", "kwacha de zambie|zk|zmw|kwachas") + .put("Dirham marocain", "dirham marocain|mad|د.م.") + .put("Dirham des Émirats arabes unis", "dirham des Émirats arabes unis|د.إ|aed") + .put("Manat azerbaïdjanais", "manat azerbaïdjanais|manat azerbaidjanais|azn") + .put("Manat turkmène", "manat turkmène|tmt|manat turkmene") + .put("Manat", "manats|manat") + .put("Qəpik", "qəpik") + .put("Shilling somalien", "shilling somalien|shillings somalien|sos") + .put("Shilling tanzanien", "shilling tanzanien|shillings tanzanien|tzs|tsh|shilling tanzanienne|shillings tanzanienne") + .put("Shilling ougandais", "shilling ougandais|shillings ougandais|ugx") + .put("Leu roumain", "leu roumain|lei|leu roumaine|ron") + .put("Leu moldave", "leu meoldave|mdl") + .put("Leu", "leu") + .put("Ban", "bani|-ban|ban") + .put("Roupie népalaise", "roupie népalaise|roupie nepalaise|npr") + .put("Roupie pakistanaise", "roupie pakistanaise|pkr") + .put("Roupie indienne", "roupie indienne|inr|roupie indien|inr|₹") + .put("Roupie seychelloise", "roupie seychelloise|scr|sr|sre") + .put("Roupie mauricienne", "roupie mauricienne|mur") + .put("Rufiyaa maldives", "rufiyaa maldives|mvr|.ރ|rf") + .put("Roupie srilankaise", "roupie srilankaise|lrk|රු|ரூ") + .put("Rupiah Indonésie", "rupia indonésie|rupia indonesie|rupiah|rp|idr") + .put("Roupie", "roupie") + .put("Couronne danoise", "couronne danoise|dkk|couronnes danoise|couronne danemark|couronnes danemark") + .put("Couronne norvégienne", "couronne norvégienne|couronne norvegienne|couronnes norvégienne|couronnes norvegienne|nok") + .put("Couronne féroïenne", "couronne féroïenne|couronne feroienne") + .put("Couronne suédoise", "couronne suédoise|couronne suéde|sek|couronnes suédoise|couronne suedoise") + .put("Couronne", "couronne|couronnes") + .put("Øre", "Øre|oyra|eyrir") + .put("Franc CFA de l'Afrique de l'Ouest", "franc cfa de l''afrique de l''ouest|franc cfa ouest africain|franc cfa|francs cfa|fcfa|frs cfa|cfa francs|xof") + .put("Franc CFA d'Afrique centrale", "franc cfa d''afrique centrale|franc cfa centrale|frs cfa centrale|xaf") + .put("Franc comorien", "franc comorien|kmf") + .put("Franc congolais", "franc congolais|cdf") + .put("Franc burundais", "franc burundais|bif") + .put("Franc djiboutienne", "franc djiboutienne|djf") + .put("Franc CFP", "franc cfp|xpf") + .put("Franc guinéen", "franc guinéen|gnf") + .put("Franc Suisse", "franc suisse|chf|sfr.|francs suisses") + .put("Franc rwandais", "franc rwandais|rwf|rw|r₣|frw") + .put("Franc belge", "franc belge|bi.|b.fr.|bef") + .put("Rappen", "rappen|-rappen") + .put("Franc", "francs|franc|fr.|fs") + .put("Centimes", "centimes|centime|santim") + .put("Rouble russe", "rouble russe|rub|₽|₽ russe|roubles russe|roubles russes|₽ russes") + .put("Nouveau rouble biélorusse", "nouveau rouble biélorusse|byn|nouveau roubles biélorusse|nouveau rouble bielorusse|nouveau roubles biélorusse") + .put("Rouble transnistriens", "rouble transnistriens|prb") + .put("Rouble biélorusses", "rouble biélorusses|roubles biélorusses|rouble bielorusses|roubles bielorusses") + .put("Kopek", "kopek|kopeks") + .put("Kapyeyka", "kapyeyka") + .put("Rouble", "roubles|rouble|br") + .put("Dinar algérien", "dinar algérien|د.ج|dzd|dinars algérien|dinar algerien|dinars algerien") + .put("Dinar de bahreïn", "dinar de bahreïn|bhd|.د.ب|dinar de bahrein") + .put("Santeem", "santeem|santeems") + .put("Dinar iraquien", "dinar iraquien|dinars iraquien|iqd|ع.د|dinar iraquienne|dinars iraquienne") + .put("Dinar jordanien", "dinar jordanien|dinars jordanien|د.ا|jod") + .put("Dinar koweïtien", "dinar koweïtien|dinar koweitien|dinars koweïtien|kwd|د.ك") + .put("Dinar libyen", "dinar libyen|dinars libyen|lyd") + .put("Dinar serbe", "dinar serbe|dinars serbe|rsd|дин.") + .put("Dinar tunisien", "dinar tunisien|dinars tunisien|tnd") + .put("Dinar yougoslave", "dinar yougoslave|dinars yougoslave|yun") + .put("Dinar", "dinars|dinar|denar|-dinars|-dinar") + .put("Fils", "fils|fulūs|-fils|-fil") + .put("Para", "para|napa") + .put("Millime", "millimes|millime") + .put("Peso argentin", "peso argentin|ars|pesos argentin|peso argentine|pesos argentine") + .put("Peso chilien", "peso chilien|pesos chilien|clp") + .put("Peso colombien", "peso colombien|pesos colombien|cop|peso colombie|pesos colombien") + .put("Peso cubains convertibles", "peso cubains convertibles|pesos cubains convertibles|cuc") + .put("Peso cubains", "peso cubaines|pesos cubaines|peso cubaine|pesos cubaines|cup") + .put("Peso dominicain", "peso dominicain|pesos dominicain|dop|peso dominicaine|pesos dominicaine") + .put("Peso philippin", "peso philippin|pesos philippin|piso|₱|php") + .put("Peso uruguayen", "peso uruguayen|pesos uruguayen|uyu") + .put("Peso", "pesos|Peso") + .put("Centavo", "centavos|Centavo") + .put("Livre britannique", "livre britannique|livres britannique|gbp|£ britannique") + .put("Livre guernesey", "livre guernesey|£ guernesey|ggp") + .put("Livre ascension", "livre ascension|livres ascension|£ ascension") + .put("Livre sainte-hélène", "livre de sainte-hélène|livre sainte-hélène|livre sainte-helene|livre de sainte hélène|shp") + .put("Livre égyptienne", "livre égyptienne|livre egyptienne|egp|ج.م") + .put("Livre des îles falkland", "livre des îles falkland|livre des iles falkland|fkp|£ iles falkland") + .put("Livre gibraltar", "livre gibraltar|livre de gibraltar|£ gibraltar|gip") + .put("Livre manx", "imp|livre manx|£ manx") + .put("Livre jersey", "livre de jersey|livre jersey|jep|£ jersey") + .put("Livre libanaise", "livre libanaise|£ libanaise|livres libanaise|lbp|ل.ل") + .put("Livre des îles malouines", "livre des îles malouines|livre des iles malouines|£ iles malouines") + .put("Livre sud-soudanaise", "livre sud-soudanaise|livre sud soudanaise|livre du soudan du sud|livres sud-soudanaises|livre sud soudan|livre soudan sud") + .put("Livre soudanaise", "livre soudanaise|livres soudanaise|sdg|£ soudan|ج.س.|livre soudan|livres soudan") + .put("Livre syrienne", "livre syrienne|ل.س|syp|livre syrie|livres syrie|£ syrie") + .put("Livre", "livre|livres|-livre|-livres|£") + .put("Pence", "pence") + .put("Shilling", "shilling|shillings|sh") + .put("Penny", "penny|sou") + .put("Dollar États-Unis", "dollar américain|$ américain|$ americain|usd|$usd|$ usd|dollar americain|dollar États-Unis|dollar des États-Unis|dollar États Unis|dollar etats unis|dollar etats-unis|$ etats-unis|$ États-Unis") + .put("Dollar des Caraïbes orientales", "dollar des caraïbes orientales|dollar des caraibes orientales|xcd|$ caraibes orientales|$ caraïbes orientales") + .put("Dollar Australien", "dollar australien|dollars australiens|$ australien|aud|$australien|australien $|$ australie|dollar australie") + .put("Dollar des bahamas", "dollar des bahamas|dollar bahamas|$ bahamas|bsd|bahama $|dollar bahama|$ bahamas") + .put("Dollar des bermudes", "dollar des bermudes|dollar bermude|dollar bermudes|$ bermudes|bmd") + .put("Dollar de belize", "dollar de Belize|dollar belizien|bzd|$ belize") + .put("Dollar îles Vierges britanniques", "dollar îles vierges britanniques|dollar iles vierges britanniques|$ iles vierges britanniques") + .put("Dollar de brunei", "dollar de brunei|$ brunei|bnd|dollar brunei") + .put("Sen", "sen") + .put("Dollar de Singapour", "dollar de singapour|dollar singapour|$ sinapour|sgd|$s") + .put("Dollar Canadien", "dollar canadien|dollars canadien|$ canadien|cad|$can|$c|$ c|dollar canada|dollar canadienne|$ canada|$cad|cad$") + .put("Dollar des îles Caïmans", "dollars des îles caïmanes|dollar des îles caïmanes|dollars des iles caimanes|dollar iles caimanes|kyd|$ci") + .put("Dollar néo-zélandais", "dollar néo-zélandais|dollar néo zélandais|dollar neo-zelandais|dollar neo zelandais|$nz|$ néo-zélandais|$ neo zelandais") + .put("Dollar îles cook", "dollar îles cook|dollar iles cook|$ iles cook") + .put("Dollar des fidji", "dollar des fidji|$ fidji|dollar fidji|dollar de fidji|dollars des fidji|dollars de fidji") + .put("Dollar guyanien", "dollar guyanien|dollar du guyana|dollar dre guyana|$ guayana|gyd|$gy") + .put("Dollar de Hong Kong", "dollar hong kong|dollar hongkong|dollar de hong kong|dollar de hongkong|$hk|$ hk|hkd|hk $|hk$|dollar hk|$hongkong|dollars hongkong|dollars hong kong") + .put("Dollar jamaïcain", "dollar jamaïcain|dollars jamaïcain|dollar jamaicain|dollars jamaicain|$j|$ jamaïque|dollar jamaïque|jmd") + .put("Dollar libérien", "dollar libérien|dollars libérien|dollar liberien|dollars liberien|lrd|$ libérien|$ liberia|$ liberien") + .put("Dollar namibien", "dollar namibien|dollars namibien|$ namibien|nad|$n|dollar namibie|dollars namibie|$ namibie") + .put("Dollar des îles Salomon", "dollar des îles Salomon|dollar des iles salomon|$si|sbd|$ iles salomon|$ îles salomon") + .put("Dollar du suriname", "dollar du suriname|srd|$ du suriname|$ suriname|dollar suriname|dollars suriname|dollars du suriname") + .put("Nouveau dollar de Taïwan", "nouveau dollar de taïwan|nouveau dollar de taiwan|twd|ntd|$nt") + .put("Dollar trinidadien", "dollar trinidadien|dollars trinidadien|ttd|$ trinidadien") + .put("Dollar", "dollar|$|dollars") + .put("Yuan Chinois", "yuan|yuans|yuan chinois|renminbi|cny|rmb|¥") + .put("Fen", "fen") + .put("Jiao", "jiao") + .put("Mark Finlandais", "marks finlandais|mark finlandais|fim|mark") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("Dollar États-Unis", "$us|usd|us$") + .put("Dollar des Caraïbes orientales", "xcd|$ec") + .put("Dollar Australien", "a$|$a|aud") + .put("Dollar des bahamas", "bsd|b$") + .put("Dollar barbadien", "bbd|bds$") + .put("Dollar de belize", "bz$|bzd") + .put("Dollar des bermudes", "bd$|bmd") + .put("Dollar de brunei", "brunei $|bnd") + .put("Dollar de Singapour", "s$|sgd") + .put("Dollar Canadien", "cad|$ ca|$ca|$ c") + .put("Dollar des îles Caïmans", "ci$|kyd") + .put("Dollar néo-zélandais", "nz$|nzd") + .put("Dollar de Fidji", "$fj|fjd") + .put("Dollar guyanien", "g$|gyd") + .put("Dollar de Hong Kong", "hkd|hk$") + .put("Dollar jamaïcain", "j$|jmd") + .put("Dollar libérien", "lrd|l$") + .put("Dollar namibien", "nad|n$") + .put("Dollar des îles Salomon", "$ si|$si|sbd") + .put("Nouveau dollar de Taïwan", "nt$|twd") + .put("Réal brésilien", "r$|brl|reais") + .put("Guaraní paraguayen", "₲|gs.|pyg") + .put("Dollar trinidadien", "ttd|titis") + .put("Yuan Chinois", "cny|rmb|¥|元") + .put("Yen Japonais", "¥|jpy") + .put("Euro", "€|eur") + .put("Livre", "£") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("din.", "kina", "lari", "taka", "tala", "vatu", "yuan", "bob", "btn", "cop", "cup", "dop", "gip", "jod", "kgs", "lak", "mga", "mop", "nad", "omr", "sar", "sbd", "scr", "sdg", "sek", "sos", "std", "try", "yer"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("Bit", "-bit|bit|bits") + .put("Kilobit", "kilobit|kilobits|kb|kbit|kbits") + .put("Megabit", "megabit|megabits|Mb|Mbit|mégabit|mégabits") + .put("Gigabit", "gigabit|gigabits|Gb|Gbit") + .put("Terabit", "terabit|terabits|Tb|Tbit|térabit|térabits") + .put("Petabit", "petabit|petabits|Pb|Pbit|pétabit|pétabits") + .put("octet", "octet|octets|-octet") + .put("Kilooctet", "kilo-octet|kilo-octets|kilooctet|kilooctets|ko|kio|kB|KiB|kilobyte|kilobytes") + .put("Mégaoctet", "mégaoctet|mégaoctets|méga-octet|méga-octets|Mo|Mio|MB|mégabyte|mégabytes") + .put("Gigaoctet", "gigaoctet|gigaoctets|Go|Gio|GB|GiB|gigabyte|gigabytes") + .put("Téraoctet", "téraoctet|téraoctets|To|Tio|TB|TiB|térabyte|térabytes") + .put("Pétaoctet", "pétaoctet|pétaoctets|Po|Pio|PB|PiB|pétabyte|pétabytes") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("mi", "barils", "grain", "pierre", "fps", "pts"); + + public static final String BuildPrefix = "(?<=(\\s|^|\\P{L}))"; + + public static final String BuildSuffix = "(?=(\\s|\\P{L}|$))"; + + public static final String ConnectorToken = "de"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilomètres", "km|kilomètres|kilomètre|kilometres|kilometre|-km") + .put("Hectomètre", "hm|hectomètre|hectomètres|hectometre|hectometres|-hm") + .put("Décamètre", "dam|décamètre|décamètres|decametre|decametres|-dm") + .put("Mètres", "m|mètres|mètre|metres|metre|m.|-m") + .put("Décimètres", "dm|décimètres|décimètre|decimetres|decimetre") + .put("Centimètres", "cm|centimètres|centimètre|centimetres|centimetre") + .put("Millimètres", "mm|millimètres|millimètre|millimetre|millimetres") + .put("Micromètres", "µm|um|micromètres|micromètre|micrometres|micrometre") + .put("Nanomètres", "nm|nanometre|nanometres|nanomètres|nanomètre") + .put("Picomètres", "pm|picomètre|picomètres|picometres|picometre") + .put("Mile", "mi|mile|miles") + .put("Pied", "pied|pieds") + .put("Yard", "yards|yard|yd") + .put("Pouce", "pouce|pouces") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("m", "m.", "yard", "yards", "pm", "pouce", "pt", "pts"); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Mètre par seconde", "m/s|metres/seconde|metres par seconde|metre par seconde|metres par secondes|mètre par seconde|mètres par seconde|mètres par secondes") + .put("Kilomètre par heure", "km/h|kilomètre par heure|kilomètres par heure|kilomètres par heures|kilometres par heure|kilometre par heure") + .put("Kilomètre par minute", "km/m|kilomètre par minute|kilomètres par minute|kilomètres par minutes|kilometre par minute|kilometre par minutes") + .put("Kilomètre par seconde", "km/s|km à la seconde|km a la seconde|kilomètre par seconde|kilomètres par seconde|kilometre par seconde|kilometres par seconde") + .put("Miles par heure", "mph|miles par heure|miles à l'heure|miles a l'heure|miles un heure") + .put("Noeuds", "noeud|noeuds|nuds") + .put("Pied par seconde", "ft/s|pied par seconde|pieds par seconde|pied/s|pieds/s") + .put("Pied par minute", "pieds/minute|pied/minute|ft/minute|ft/min|pied/min") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("Kelvin", "k|K|kelvin") + .put("F", "°f|° f|degres f|degrés f|deg f|degrés fahrenheit|degres fahrenheit|fahrenheit|deg fahrenheit|degs fahrenheit") + .put("R", "rankine|°r|° r") + .put("C", "°c|° c|degres c|degrés c|deg c|degrés celsius|degres celsius|celsius|deg celsius|degs celsius|centigrade|deg centigrade|degs centigrade|degrés centigrade|degres centigrade|degré centigrade|degre centigrade") + .put("Degré", "degrés|degres|deg.|°|degré|degre|deg|degs") + .build(); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Mètre cube", "m3|m^3|m³|mètre cube|mètres cube|metre cube|metres cube") + .put("Centimètre cube", "cm3|cm^3|cm³|centimètre cube|centimètres cube|centimetre cube|centimetres cube") + .put("Millimètre cube", "mm3|mm^3|mm³|millimètre cube|millimètres cube|millimetre cube|millimetres cube") + .put("Kilomètre cube", "km3|km^3|km³|kilomètre cube|kilomètres cube|kilometre cube|kilometres cube") + .put("Pieds cube", "pieds cubes|pieds cube|pied cube|pied cubes") + .put("Litre", "litre|litres|lts|l") + .put("Millilitre", "ml|millilitre|millilitres") + .put("Gallon", "gallon|gallons") + .put("Pintes", "pintes") + .put("Onces", "onces|once|oz") + .put("Décilitre", "dl|décilitre|decilitre|décilitres|decilitres") + .put("Centilitre", "cl|centilitres|centilitre") + .put("Onces liquides", "onces liquides|once liquide|once liquides") + .put("Baril", "baril|barils|bbl") + .build(); + + public static final List AmbiguousVolumeUnitList = Arrays.asList("oz", "l"); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Kilogramme", "kg|kilogramme|kilogrammes|kilo|kilos") + .put("Gram", "g|gramme|grammes") + .put("Milligramme", "mg|milligramme|milligrammes") + .put("Tonne métrique", "tonne métrique|tonnes métrique|tonnes métriques|tonne metrique|tonnes metrique") + .put("Tonne", "tonne|tonnes|-tonnes|-tonne") + .put("Livre", "livre|livres") + .build(); + + public static final List AmbiguousWeightUnitList = Arrays.asList("g", "oz"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("\\bcent\\b", "\\bpour\\s+cent\\b") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java new file mode 100644 index 000000000..f8d694c55 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java @@ -0,0 +1,451 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class GermanNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "jahr alt|jahre alt|jahren|jahre|lebensjahr") + .put("Month", "monat alt|monate alt|monaten|monate") + .put("Week", "woche alt|wochen alt|wochen|woche") + .put("Day", "tag alt|tage alt|tagen|tage") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("jahren", "jahre", "monaten", "monate", "wochen", "woche", "tagen", "tage"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Square kilometer", "qkm|quadratkilometer|km^2|km²") + .put("Square hectometer", "qhm|quadrathektometer|hm^2|hm²|hektar") + .put("Square decameter", "quadratdekameter|dam^2|dam²") + .put("Square meter", "qm|quadratmeter|m^2|m²") + .put("Square decimeter", "qdm|quadratdezimeter|dm^2|dm²") + .put("Square centimeter", "qcm|quadratzentimeter|cm^2|cm²") + .put("Square millimeter", "qmm|quadratmillimeter|mm^2|mm²") + .put("Square inch", "sqin|quadratzoll|in^2|in²") + .put("Square foot", "sqft|quadratfuß|fuß^2|fuß²|ft2|ft^2|ft²") + .put("Square mile", "sqmi|quadratmeile|mi^2|mi²") + .put("Square yard", "sqyd|quadratyard|yd^2|yd²") + .put("Acre", "-acre|acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Abkhazian apsar", "abkhazian apsar|apsars") + .put("Afghan afghani", "afghanischer afghani|afghanische afghani|afghanischen afghani|؋|afn|afghani") + .put("Pul", "pul") + .put("Euro", "euro|€|eur") + .put("Cent", "cent|-cent") + .put("Albanian lek", "albaninischer Lek|albanische Lek|albanischen Lek") + .put("Qindarkë", "qindarkë|qindarkës|qindarke|qindarkes") + .put("Angolan kwanza", "angolanischer kwanza|angolanische kwanza|angolanischen kwanza|kz|aoa|kwanza|kwanzas") + .put("Armenian dram", "armeninischer dram|armeninische dram|armeninischen dram") + .put("Aruban florin", "Aruba-Florin|ƒ|awg") + .put("Bangladeshi taka", "bangladesischer taka|bengalischer taka|bangladesische taka|bengalische taka|bangladesischen taka|bengalischen taka|৳|bdt|taka") + .put("Paisa", "poisha|paisa") + .put("Bhutanese ngultrum", "bhutanischer ngultrum|bhutanische ngultrum|bhutanischen ngultrum|nu.|btn") + .put("Chetrum", "chetrum") + .put("Bolivian boliviano", "bolivianischer boliviano|bolivianische boliviano|bolivianischen boliviano|bob|bs.|boliviano") + .put("Bosnia and Herzegovina convertible mark", "bosnischer konvertible mark|bosnisch-herzegowinischer konvertible mark|bosnische konvertible mark|bosnisch-herzegowinische konvertible mark|bosnischen konvertible mark|bosnisch-herzegowinischen konvertible mark|konvertible mark|bam") + .put("Fening", "Fening") + .put("Botswana pula", "botswanischer pula|botswanische pula|botswanischen pula|bwp|pula") + .put("Thebe", "thebe") + .put("Brazilian real", "brazilianischer real|brazilianische real|brazilianischen real|r$|brl|real") + .put("Bulgarian lev", "bulgarischer lew|bulgarische lew|bulgarischen lew|bgn|лв|lew") + .put("Stotinka", "stotinki|stotinka") + .put("Cambodian riel", "kambodschanischer riel|kambodschanische riel|kambodschanischen riel|khr|៛|riel") + .put("Cape Verdean escudo", "kap-verde-escudo|cve") + .put("Costa Rican colón", "costa-rica-colón|costa-rica-colon|crc|₡") + .put("Salvadoran colón", "svc|el-salvador-colón|el-salvador-colon") + .put("Céntimo", "céntimo") + .put("Croatian kuna", "kroatischer kuna|kroatische kuna|kroatischen kuna|kn|hrk|kuna") + .put("Lipa", "lipa") + .put("Czech koruna", "tschechische krone|tschechischen kronen|tschechischer kronen|czk|kč") + .put("Haléř", "haléř") + .put("Eritrean nakfa", "eritreischer nakfa|eritreische nakfa|eritreischen nakfa|nfk|ern|nakfa") + .put("Ethiopian birr", "äthiopischer birr|äthiopische birr|äthiopischen birr|etb") + .put("Gambian dalasi", "gambischer dalasi|gambische dalasi|gambischen dalasi|gmd") + .put("Butut", "bututs|butut") + .put("Georgian lari", "georgischer lari|georgische lari|georgischen lari|lari|gel|₾") + .put("Tetri", "tetri") + .put("Ghanaian cedi", "ghanaischer cedi|ghanaische cedi|ghanaischen cedi|Ghana cedi|ghs|₵|gh₵") + .put("Pesewa", "pesewas|pesewa") + .put("Guatemalan quetzal", "guatemaltekischer quetzal|guatemaltekische quetzal|guatemaltekischen quetzal|gtq|quetzal") + .put("Haitian gourde", "haitianischer gourde|haitianische gourde|haitianischen gourde|htg") + .put("Honduran lempira", "honduranischer lempira|honduranische lempira|honduranischen lempira|hnl") + .put("Hungarian forint", "ungarischer forint|ungarische forint|ungarischen forint|huf|ft|forint") + .put("Fillér", "fillér") + .put("Iranian rial", "iranischer rial|iranische rial|iranischen rial|irr") + .put("Yemeni rial", "jemen-rial|yer") + .put("Israeli new shekel", "₪|ils|agora") + .put("Lithuanian litas", "ltl|litauischer litas|litauische litas|litauischen litas") + .put("Japanese yen", "japaneser yen|japanese yen|japanesen yen|jpy|yen|¥") + .put("Kazakhstani tenge", "kasachischer tenge|kasachische tenge|kasachischen tenge|kzt") + .put("Kenyan shilling", "kenia-schilling|kes") + .put("North Korean won", "nordkoreanischer won|nordkoreanische won|nordkoreanischen won|kpw") + .put("South Korean won", "südkoreanischer won|südkoreanische won|südkoreanischen won|krw") + .put("Korean won", "koreanischer won|koreanische won|koreanischen won|₩") + .put("Kyrgyzstani som", "kirgisischer som|kirgisische som|kirgisischen som|kgs") + .put("Uzbekitan som", "usbekischer som|usbekische som|usbekischen som|usbekischer sum|usbekische sum|usbekischen sum|usbekischer so'm|usbekische so'm|usbekischen so'm|usbekischer soum|usbekische soum|usbekischen soum|uzs") + .put("Lao kip", "laotischer kip|laotische kip|laotischen kip|lak|₭n|₭") + .put("Att", "att") + .put("Lesotho loti", "lesothischer loti|lesothische loti|lesothischen loti|lsl|loti") + .put("Sente", "sente|lisente") + .put("South African rand", "südafrikanischer rand|südafrikanische rand|südafrikanischen rand|zar") + .put("Macanese pataca", "macao-pataca|mop$|mop") + .put("Avo", "avos|avo") + .put("Macedonian denar", "mazedonischer denar|mazedonische denar|mazedonischen denar|mkd|ден") + .put("Deni", "deni") + .put("Malagasy ariary", "madagassischer ariary|madagassische ariary|madagassischen ariary|ariary|mga") + .put("Iraimbilanja", "iraimbilanja") + .put("Malawian kwacha", "malawi-kwacha|mk|mwk") + .put("Tambala", "tambala") + .put("Malaysian ringgit", "malaysischer ringgit|malaysische ringgit|malaysischen ringgit|rm|myr") + .put("Mauritanian ouguiya", "mauretanischer ouguiya|mauretanische ouguiya|mauretanischen ouguiya|mro") + .put("Khoums", "khoums") + .put("Mongolian tögrög", "mongolischer tögrög|mongolische tögrög|mongolischen tögrög|mongolischer tugrik|mongolische tugrik|mongolischen tugrik|mnt|₮") + .put("Mozambican metical", "mosambik-metical|mosambik metical|mt|mzn") + .put("Burmese kyat", "myanmar-kyat|myanmar kyat|ks|mmk") + .put("Pya", "pya") + .put("Nicaraguan córdoba", "nicaraguanischer córdoba oro|nicaraguanische córdoba oro|nicaraguanischen córdoba oro|nicaraguanischer córdoba|nicaraguanische córdoba|nicaraguanischen córdoba|nio|córdoba|córdoba oro") + .put("Nigerian naira", "nigerianischer naira|nigerianische naira|nigerianischen naira|naira|ngn|₦|nigeria naira") + .put("Kobo", "kobo") + .put("Turkish lira", "türkischer lira|türkische lira|türkischen lira|tuerkischer lira|tuerkische lira|tuerkischen lira|try|tl") + .put("Kuruş", "kuruş") + .put("Omani rial", "omanischer rial|omanische rial|omanischen rial|omr|ر.ع.") + .put("Panamanian balboa", "panamaischer balboa|panamaische balboa|panamaischen balboa|b/.|pab") + .put("Centesimo", "centesimo") + .put("Papua New Guinean kina", "papua-neuguinea-kina|kina|pgk") + .put("Toea", "toea") + .put("Paraguayan guaraní", "paraguayischer guaraní|paraguayische guaraní|paraguayischen guaraní|guaraní|₲|pyg") + .put("Peruvian sol", "peruanischer sol|peruanische sol|peruanischen sol|soles|sol") + .put("Polish złoty", "polnischer złoty|polnische złoty|polnischen złoty|polnischer zloty|polnische zloty|polnischen zloty|zł|pln|złoty|zloty") + .put("Grosz", "groszy|grosz|grosze") + .put("Qatari riyal", "katar-riyal|katar riyal|qatari riyal|qar") + .put("Saudi riyal", "saudi-riyal|sar") + .put("Riyal", "riyal|﷼") + .put("Dirham", "dirham|dirhem|dirhm") + .put("Halala", "halalas|halala") + .put("Samoan tālā", "samoanischer tala|samoanische tala|samoanischen tala|samoanischer tālā|samoanische tālā|samoanischen tālā|tālā|tala|ws$|samoa|wst|samoa-tālā|samoa-tala") + .put("Sene", "sene") + .put("São Tomé and Príncipe dobra", "são-toméischer dobra|são-toméische dobra|são-toméischen dobra|dobra|std") + .put("Sierra Leonean leone", "sierra-leonischer leone|sierra-leonische leone|sierra-leonischen leone|sll|leone|le") + .put("Peseta", "pesetas|peseta") + .put("Netherlands guilder", "florin|antillen-gulden|niederländische-antillen-gulden|antillen gulden|ang|niederländischer gulden|niederländische gulden|niederländischen gulden|gulden|fl") + .put("Swazi lilangeni", "swazi-lilangeni|swazi lilangeni|lilangeni|szl|swazi-emalangeni|swazi emalangeni") + .put("Tajikistani somoni", "tadschikischer somoni|tadschikische somoni|tadschikischen somoni|tadschikistan-somoni|tadschikistan somoni|tajikischer somoni|tajikische somoni|tajikischen somoni|tajikistan-somoni|tajikistan somoni|tjs") + .put("Diram", "dirams|diram") + .put("Thai baht", "thailändischer baht|thailändische baht|thailändischen baht|thailaendischer baht|thailaendische baht|thailaendischen baht|thai baht|thai-baht|฿|thb") + .put("Satang", "satang|satangs") + .put("Tongan paʻanga", "tongaischer paʻanga|tongaische paʻanga|tongaischen paʻanga|paʻanga|tonga paʻanga|tongaischer pa'anga|tongaische pa'anga|tongaischen pa'anga|pa'anga|tonga pa'anga") + .put("Seniti", "seniti") + .put("Ukrainian hryvnia", "ukrainischer hrywnja|ukrainische hrywnja|ukrainischen hrywnja|hrywnja|uah|₴") + .put("Vanuatu vatu", "vanuatu-vatu|vanuatu vatu|vatu|vuv") + .put("Venezuelan bolívar", "venezolanischer bolívar|venezolanische bolívar|venezolanischen bolívar|bs.f.|vef") + .put("Vietnamese dong", "vietnamesischer đồng|vietnamesische đồng|vietnamesischen đồng|vietnamesischer dong|vietnamesische dong|vietnamesischen dong|vnd|đồng") + .put("Zambian kwacha", "sambischer kwacha|sambische kwacha|sambischen kwacha|zk|zmw") + .put("Moroccan dirham", "marokkanischer dirham|marokkanische dirham|marokkanischen dirham|mad|د.م.") + .put("United Arab Emirates dirham", "vae dirham|vae-dirham|dirham der vereinigten arabischen emirate|د.إ|aed") + .put("Azerbaijani manat", "aserbaidschan-manat|azn") + .put("Turkmenistan manat", "turkmenistan-manat|tmt") + .put("Manat", "manat") + .put("Qəpik", "qəpik") + .put("Somali shilling", "somalia-schilling|sh.so.|sos") + .put("Somaliland shilling", "somaliland-schilling") + .put("Tanzanian shilling", "tansania-schilling|tsh|tzs") + .put("Ugandan shilling", "uganda-schilling|ugx") + .put("Romanian leu", "rumänischer leu|rumänische leu|rumänischen leu|rumaenischer leu|rumaenische leu|rumaenischen leu|lei|ron") + .put("Moldovan leu", "moldauischer leu|moldauische leu|moldauischen leu|mdl|moldau leu") + .put("Leu", "leu") + .put("Ban", "bani|ban") + .put("Nepalese rupee", "nepalesischer rupie|nepalesische rupie|nepalesischen rupie|nepalesische rupien|nepalesischer rupien|nepalesischen rupien|npr") + .put("Pakistani rupee", "pakistanischer rupie|pakistanische rupie|pakistanischen rupie|pakistanischer rupien|pakistanische rupien|pakistanischen rupien|pkr") + .put("Indian rupee", "indischer rupie|indische rupie|indischen rupie|indischer rupien|indische rupien|indischen rupien|inr|₹") + .put("Seychellois rupee", "seychellen-rupie|seychellen-rupien|scr|sr|sre") + .put("Mauritian rupee", "mauritius-rupie|mauritius-rupien|mur") + .put("Maldivian rufiyaa", "maledivischer rufiyaa|maledivische rufiyaa|maledivischen rufiyaa|mvr|.ރ") + .put("Sri Lankan rupee", "sri-lanka-rupie|sri-lanka-rupien|lkr|රු|ரூ") + .put("Indonesian rupiah", "indonesischer rupiah|indonesische rupiah|indonesischen rupiah|rupiah|perak|rp|idr") + .put("Rupee", "rupie|rs") + .put("Danish krone", "dänische krone|dänischen krone|dänischer kronen|dänische kronen|dänischen kronen|daenische krone|daenischen krone|daenischer kronen|daenische kronen|daenischen kronen|dkk") + .put("Norwegian krone", "norwegische krone|norwegischen krone|norwegischer kronen|norwegische kronen|norwegischen kronen|nok") + .put("Faroese króna", "färöische króna|färöische krone|färöischen krone|färöischer kronen|färöische kronen|färöischen kronen") + .put("Icelandic króna", "isländische krone|isländischen krone|isländischer kronen|isländische kronen|isländischen kronen|isk") + .put("Swedish krona", "schwedische krone|schwedischen krone|schwedischer kronen|schwedische kronen|schwedischen kronen|sek") + .put("Krone", "krone|kronen|kr|-kr") + .put("Øre", "Øre|oyra|eyrir") + .put("West African CFA franc", "west african cfa franc|xof|westafrikanische cfa franc|westafrikanische-cfa-franc") + .put("Central African CFA franc", "central african cfa franc|xaf|zentralafrikanische cfa franc|zentralafrikanische-cfa-franc") + .put("Comorian franc", "komoren-franc|kmf") + .put("Congolese franc", "kongo-franc|cdf") + .put("Burundian franc", "burundi-franc|bif") + .put("Djiboutian franc", "dschibuti-franc|djf") + .put("CFP franc", "cfp-franc|xpf") + .put("Guinean franc", "franc guinéen|franc-guinéen|gnf") + .put("Swiss franc", "schweizer franken|schweizer-franken|chf|sfr.") + .put("Rwandan franc", "ruanda-franc|rwf|rf|r₣|frw") + .put("Belgian franc", "belgischer franken|belgische franken|belgischen franken|bi.|b.fr.|bef") + .put("Rappen", "rappen|-rappen") + .put("Franc", "franc|französischer franc|französische franc|französischen franc|französischer franken|französische franken|französischen franken|franken|fr.|fs") + .put("Centime", "centimes|centime|santim") + .put("Russian ruble", "russischer rubel|russische rubel|russischen rubel|₽|rub") + .put("New Belarusian ruble", "neuer weißrussischer rubel|neue weißrussische rubel|neuen weißrussischen rubel|neuem weißrussischen rubel") + .put("Old Belarusian ruble", "alter weißrussischer rubel|alte weißrussische rubel|alten weißrussischen rubel|altem weißrussischen rubel") + .put("Transnistrian ruble", "transnistrischer rubel|transnistrische rubel|transnistrischen rubel|prb|р.") + .put("Belarusian ruble", "weißrussischer rubel|weißrussische rubel|weißrussischen rubel") + .put("Kopek", "kopek|kopeks") + .put("Kapyeyka", "kapyeyka") + .put("Ruble", "rubel|br") + .put("Algerian dinar", "algerischer dinar|algerische dinar|algerischen dinar|د.ج|dzd") + .put("Bahraini dinar", "bahrain-dinar|bhd|.د.ب") + .put("Santeem", "santeem|santeeme") + .put("Iraqi dinar", "irakischer dinar|irakische dinar|irakischen dinar|iqd|ع.د") + .put("Jordanian dinar", "jordanischer dinar|jordanische dinar|jordanischen dinar|د.ا|jod") + .put("Kuwaiti dinar", "kuwait-dinar|kwd|د.ك") + .put("Libyan dinar", "libyscher dinar|libysche dinar|libyschen dinar|lyd") + .put("Serbian dinar", "serbischer dinar|serbische dinar|serbischen dinar|din.|rsd|дин.") + .put("Tunisian dinar", "tunesischer dinar|tunesische dinar|tunesischen dinar|tnd") + .put("Yugoslav dinar", "jugoslawischer dinar|jugoslawische dinar|jugoslawischen dinar|yun") + .put("Dinar", "dinar|denar") + .put("Fils", "fils|fulūs") + .put("Para", "para|napa") + .put("Millime", "millime") + .put("Argentine peso", "argentinischer peso|argentinische peso|argentinischen peso|ars") + .put("Chilean peso", "chilenischer peso|chilenische peso|chilenischen peso|clp") + .put("Colombian peso", "kolumbianischer peso|kolumbianische peso|kolumbianischen peso|cop") + .put("Cuban convertible peso", "kubanischer peso convertible|kubanische peso convertible|kubanischen peso convertible|peso convertible|cuc") + .put("Cuban peso", "kubanischer peso|kubanische peso|kubanischen peso|cup") + .put("Dominican peso", "dominican pesos|dominican peso|dop|dominica pesos|dominica peso") + .put("Mexican peso", "mexikanischer peso|mexikanische peso|mexikanischen peso|mxn") + .put("Philippine peso", "piso|philippinischer peso|philippinische peso|philippinischen peso|₱|php") + .put("Uruguayan peso", "uruguayischer peso|uruguayische peso|uruguayischen peso|uyu") + .put("Peso", "peso") + .put("Centavo", "centavos|centavo") + .put("Alderney pound", "alderney pfund|alderney £") + .put("British pound", "britischer pfund|britische pfund|britischen pfund|british £|gbp|pfund sterling") + .put("Guernsey pound", "guernsey-pfund|guernsey £|ggp") + .put("Ascension pound", "ascension-pfund|ascension pound|ascension £") + .put("Saint Helena pound", "st.-helena-pfund|saint helena £|shp") + .put("Egyptian pound", "ägyptisches pfund|ägyptische pfund|ägyptischen pfund|ägyptisches £|egp|ج.م") + .put("Falkland Islands pound", "falkland-pfund|falkland £|fkp|falkland-£") + .put("Gibraltar pound", "gibraltar-pfund|gibraltar £|gibraltar-£|gip") + .put("Manx pound", "isle-of-man-pfund|isle-of-man-£|imp") + .put("Jersey pound", "jersey-pfund|jersey-£|jep") + .put("Lebanese pound", "libanesisches pfund|libanesische pfund|libanesischen pfund|libanesisches-£|lbp|ل.ل") + .put("South Georgia and the South Sandwich Islands pound", "süd-georgien & die südlichen sandwichinseln pfund|süd-georgien & die südlichen sandwichinseln £") + .put("South Sudanese pound", "südsudanesisches pfund|südsudanesische pfund|südsudanesischen pfund|südsudanesisches £|ssp|südsudanesische £") + .put("Sudanese pound", "sudanesisches pfund|sudanesische pfund|sudanesischen pfund|sudanesisches £|ج.س.|sdg|sudanesische £") + .put("Syrian pound", "syrisches pfund|syrische pfund|syrischen pfund|syrisches £|ل.س|syp|syrische £") + .put("Tristan da Cunha pound", "tristan-da-cunha-pfund|tristan-da-cunha-£") + .put("Pound", "pfund|£") + .put("Pence", "pence") + .put("Shilling", "shillings|shilling|shilingi|sh") + .put("Penny", "pennies|penny") + .put("United States dollar", "us-dollar|us$|usd|amerikanischer dollar|amerikanische dollar|amerikanischen dollar") + .put("East Caribbean dollar", "ostkaribischer dollar|ostkaribische dollar|ostkaribischen dollar|ostkaribische $|xcd") + .put("Australian dollar", "australischer dollar|australische dollar|australischen dollar|australische $|aud") + .put("Bahamian dollar", "bahama-dollar|bahama-$|bsd") + .put("Barbadian dollar", "barbados-dollar|barbados-$|bbd") + .put("Belize dollar", "belize-dollar|belize-$|bzd") + .put("Bermudian dollar", "bermuda-dollar|bermuda-$|bmd") + .put("British Virgin Islands dollar", "british virgin islands dollars|british virgin islands dollar|british virgin islands $|bvi$|virgin islands dollars|virgin islands dolalr|virgin islands $|virgin island dollars|virgin island dollar|virgin island $") + .put("Brunei dollar", "brunei-dollar|brunei $|bnd") + .put("Sen", "sen") + .put("Singapore dollar", "singapur-dollar|singapur-$|s$|sgd") + .put("Canadian dollar", "kanadischer dollar|kanadische dollar|kanadischen dollar|cad|can$|c$") + .put("Cayman Islands dollar", "kaiman-dollar|kaiman-$|kyd|ci$") + .put("New Zealand dollar", "neuseeland-dollar|neuseeland-$|nz$|nzd|kiwi") + .put("Cook Islands dollar", "cookinseln-dollar|cookinseln-$") + .put("Fijian dollar", "fidschi-dollar|fidschi-$|fjd") + .put("Guyanese dollar", "guyana-dollar|gyd|gy$") + .put("Hong Kong dollar", "hongkong-dollar|hong kong $|hk$|hkd|hk dollars|hk dollar|hk $|hongkong$") + .put("Jamaican dollar", "jamaika-dollar|jamaika-$|j$") + .put("Kiribati dollar", "kiribati-dollar|kiribati-$") + .put("Liberian dollar", "liberianischer dollar|liberianische dollar|liberianischen dollar|liberianische $|lrd") + .put("Micronesian dollar", "mikronesischer dollar|mikronesische dollar|mikronesischen dollar|mikronesische $") + .put("Namibian dollar", "namibia-dollar|namibia-$|nad|n$") + .put("Nauruan dollar", "nauru-dollar|nauru-$") + .put("Niue dollar", "niue-dollar|niue-$") + .put("Palauan dollar", "palau-dollar|palau-$") + .put("Pitcairn Islands dollar", "pitcairninseln-dollar|pitcairninseln-$") + .put("Solomon Islands dollar", "salomonen-dollar|salomonen-$|si$|sbd") + .put("Surinamese dollar", "suriname-dollar|suriname-$|srd") + .put("New Taiwan dollar", "neuer taiwan-dollar|neue taiwan-dollar|neuen taiwan-dollar|nt$|twd|ntd") + .put("Trinidad and Tobago dollar", "trinidad-und-tobago-dollar|trinidad-und-tobago-$|ttd") + .put("Tuvaluan dollar", "tuvaluischer dollar|tuvaluische dollar|tuvaluischen dollar|tuvaluische $") + .put("Dollar", "dollar|$") + .put("Chinese yuan", "yuan|chinesischer yuan|chinesische yuan|chinesischen yuan|renminbi|cny|rmb|¥") + .put("Fen", "fen") + .put("Jiao", "jiao") + .put("Finnish markka", "suomen markka|finnish markka|finsk mark|fim|markkaa|markka|finnische mark|finnischen mark") + .put("Penni", "penniä|penni") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("United States dollar", "united states $|us$|us $|u.s. $|u.s $") + .put("East Caribbean dollar", "east caribbean $") + .put("Australian dollar", "australian $|australia $") + .put("Bahamian dollar", "bahamian $|bahamia $") + .put("Barbadian dollar", "barbadian $|barbadin $") + .put("Belize dollar", "belize $") + .put("Bermudian dollar", "bermudian $") + .put("British Virgin Islands dollar", "british virgin islands $|bvi$|virgin islands $|virgin island $|british virgin island $") + .put("Brunei dollar", "brunei $|b$") + .put("Sen", "sen") + .put("Singapore dollar", "singapore $|s$") + .put("Canadian dollar", "canadian $|can$|c$|c $|canada $") + .put("Cayman Islands dollar", "cayman islands $|ci$|cayman island $") + .put("New Zealand dollar", "new zealand $|nz$|nz $") + .put("Cook Islands dollar", "cook islands $|cook island $") + .put("Fijian dollar", "fijian $|fiji $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hong kong $|hk$|hkd|hk $") + .put("Jamaican dollar", "jamaican $|j$|jamaica $") + .put("Kiribati dollar", "kiribati $") + .put("Liberian dollar", "liberian $|liberia $") + .put("Micronesian dollar", "micronesian $") + .put("Namibian dollar", "namibian $|nad|n$|namibia $") + .put("Nauruan dollar", "nauruan $") + .put("Niue dollar", "niue $") + .put("Palauan dollar", "palauan $") + .put("Pitcairn Islands dollar", "pitcairn islands $|pitcairn island $") + .put("Solomon Islands dollar", "solomon islands $|si$|si $|solomon island $") + .put("Surinamese dollar", "surinamese $|surinam $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Trinidad and Tobago dollar", "trinidad and tobago $|trinidad $|trinidadian $") + .put("Tuvaluan dollar", "tuvaluan $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .put("Turkish lira", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("din.", "kiwi", "kina", "kobo", "lari", "lipa", "napa", "para", "sfr.", "taka", "tala", "toea", "vatu", "yuan", "ang", "ban", "bob", "btn", "byr", "cad", "cop", "cup", "dop", "gip", "jod", "kgs", "lak", "lei", "mga", "mop", "nad", "omr", "pul", "sar", "sbd", "scr", "sdg", "sek", "sen", "sol", "sos", "std", "try", "yer", "yen"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("Bit", "-bit|bit|bits") + .put("Kilobit", "kilobit|kilobits|kb|kbit") + .put("Megabit", "megabit|megabits|Mb|Mbit") + .put("Gigabit", "gigabit|gigabits|Gb|Gbit") + .put("Terabit", "terabit|terabits|Tb|Tbit") + .put("Petabit", "petabit|petabits|Pb|Pbit") + .put("Byte", "byte|bytes") + .put("Kilobyte", "kilobyte|kB|kilobytes|kilo byte|kilo bytes|kByte") + .put("Megabyte", "megabyte|mB|megabytes|mega byte|mega bytes|MByte") + .put("Gigabyte", "gigabyte|gB|gigabytes|giga byte|giga bytes|GByte") + .put("Terabyte", "terabyte|tB|terabytes|tera byte|tera bytes|TByte") + .put("Petabyte", "petabyte|pB|petabytes|peta byte|peta bytes|PByte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("barrel", "grain", "gran", "grän", "korn", "pfund", "stone", "yard", "cord", "dram", "fuß", "gill", "knoten", "peck", "cup", "fps", "pts", "in", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^))"; + + public static final String BuildSuffix = "(?=(\\s|\\W|$))"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilometer", "km|kilometer|kilometern") + .put("Hectometer", "hm|hektometer|hektometern") + .put("Decameter", "dam|dekameter|dekametern") + .put("Meter", "m|meter|metern") + .put("Decimeter", "dm|dezimeter|dezimetern") + .put("Centimeter", "cm|zentimeter|centimeter|zentimetern|centimetern") + .put("Millimeter", "mm|millimeter|millimetern") + .put("Micrometer", "μm|mikrometer|mikrometern") + .put("Nanometer", "nm|nanometer|nanometern") + .put("Picometer", "pm|pikometer|picometer|pikometern|picometern") + .put("Mile", "meile|meilen") + .put("Yard", "yard|yards") + .put("Inch", "zoll|inch|in|\"") + .put("Foot", "fuß|ft") + .put("Light year", "lichtjahr|lichtjahre|lichtjahren") + .put("Pt", "pt|pts") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("m", "yard", "yards", "pm", "pt", "pts"); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Meter per second", "meter/sekunde|m/s|meter pro sekunde|metern pro sekunde") + .put("Kilometer per hour", "km/h|kilometer/stunde|kilometer pro stunde|kilometern pro stunde") + .put("Kilometer per minute", "km/min|kilometer pro minute|kilometern pro minute") + .put("Kilometer per second", "km/s|kilometer pro sekunde|kilometern pro sekunde") + .put("Mile per hour", "mph|mi/h|meilen pro stunde|meilen/stunde|meile pro stunde") + .put("Knot", "kt|knoten|kn") + .put("Foot per second", "ft/s|fuß/sekunde|fuß pro sekunde|fps") + .put("Foot per minute", "ft/min|fuß/minute|fuß pro minute") + .put("Yard per minute", "yard pro minute|yard/minute|yard/min") + .put("Yard per second", "yard pro sekunde|yard/sekunde|yard/s") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("F", "grad fahrenheit|°fahrenheit|°f|fahrenheit") + .put("K", "k|K|kelvin|grad kelvin|°kelvin|°k|°K") + .put("R", "rankine|°r") + .put("D", "delisle|°de") + .put("C", "grad celsius|°celsius|°c|celsius") + .put("Degree", "grad|°") + .build(); + + public static final List AmbiguousTemperatureUnitList = Arrays.asList("c", "f", "k"); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Cubic meter", "m3|kubikmeter|m³") + .put("Cubic centimeter", "kubikzentimeter|cm³") + .put("Cubic millimiter", "kubikmillimeter|mm³") + .put("Hectoliter", "hektoliter") + .put("Decaliter", "dekaliter") + .put("Liter", "l|liter") + .put("Deciliter", "dl|deziliter") + .put("Centiliter", "cl|zentiliter") + .put("Milliliter", "ml|mls|milliliter") + .put("Cubic yard", "kubikyard") + .put("Cubic inch", "kubikzoll") + .put("Cubic foot", "kubikfuß") + .put("Cubic mile", "kubikmeile") + .put("Fluid ounce", "fl oz|flüssigunze|fluessigunze") + .put("Teaspoon", "teelöffel|teeloeffel") + .put("Tablespoon", "esslöffel|essloeffel") + .put("Pint", "pinte") + .put("Volume unit", "fluid dram|fluid drachm|flüssigdrachme|gill|quart|minim|cord|peck|beck|scheffel|hogshead|oxhoft") + .build(); + + public static final List AmbiguousVolumeUnitList = Arrays.asList("l", "unze", "oz", "cup", "peck", "cord", "gill"); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Kilogram", "kg|kilogramm|kilo") + .put("Gram", "g|gramm") + .put("Milligram", "mg|milligramm") + .put("Barrel", "barrel") + .put("Gallon", "gallone|gallonen") + .put("Metric ton", "metrische tonne|metrische tonnen") + .put("Ton", "tonne|tonnen") + .put("Pound", "pfund|lb") + .put("Ounce", "unze|unzen|oz|ounces") + .put("Weight unit", "pennyweight|grain|british long ton|US short hundredweight|stone|dram") + .build(); + + public static final List AmbiguousWeightUnitList = Arrays.asList("g", "oz", "stone", "dram"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java new file mode 100644 index 000000000..0e8ffbc43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java @@ -0,0 +1,546 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class JapaneseNumericWithUnit { + + public static final List AgeAmbiguousValues = Arrays.asList("歳"); + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "歳") + .put("Month", "ヶ月") + .put("Week", "週間|週") + .put("Day", "日間|日齢|日大") + .build(); + + public static final String BuildPrefix = ""; + + public static final String BuildSuffix = ""; + + public static final String ConnectorToken = ""; + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Afghan afghani", "アフガニ") + .put("Pul", "プル") + .put("Euro", "ユーロ") + .put("Cent", "セント") + .put("Albanian lek", "アルバニアレク|アルバニア・レク|レク") + .put("Angolan kwanza", "アンゴラクワンザ|アンゴラ・クワンザ|クワンザ") + .put("Armenian dram", "アルメニアドラム|アルメニア・ドラム|ドラム") + .put("Aruban florin", "アルバ・フロリン|フロリン") + .put("Bangladeshi taka", "タカ|バングラデシュ・タカ") + .put("Paisa", "パイサ") + .put("Bhutanese ngultrum", "ニュルタム|ブータン・ニュルタム|ブータンニュルタム") + .put("Chetrum", "チェルタム") + .put("Bolivian boliviano", "ボリビアーノ") + .put("Bosnia and Herzegovina convertible mark", "兌換マルク") + .put("Botswana pula", "ボツワナ・プラ|ボツワナプラ|プラ") + .put("Thebe", "テベ") + .put("Brazilian real", "ブラジル・レアル|ブラジルレアル|レアル") + .put("Bulgarian lev", "ブルガリア・レフ|ブルガリアレフ|レフ") + .put("Stotinka", "ストティンカ") + .put("Cambodian riel", "カンボジア・リエル|カンボジアリエル|リエル") + .put("Cape Verdean escudo", "カーボベルデ・エスクード") + .put("Croatian kuna", "クロアチアクーナ|クロアチア・クーナ|クーナ") + .put("Lipa", "リパ") + .put("Eritrean nakfa", "エリトリア・ナクファ|エリトリアナクファ|ナクファ") + .put("Ethiopian birr", "エチオピア・ブル|エチオピアブル|ブル") + .put("Gambian dalasi", "ガンビア・ダラシ|ガンビアダラシ|ダラシ") + .put("Butut", "ブトゥツ") + .put("Georgian lari", "ジョージア・ラリ|ジョージアラリ|ラリ") + .put("Tetri", "テトリ") + .put("Ghanaian cedi", "ガーナ・セディ|ガーナセディ|セディ") + .put("Pesewa", "ペセワ") + .put("Guatemalan quetzal", "グアテマラ・ケツァル|グアテマラケツァル|ケツァル") + .put("Haitian gourde", "ハイチ・グールド|ハイチグールド|グールド") + .put("Honduran lempira", "ホンジュラス・レンピラ|ホンジュラスレンピラ|レンピラ") + .put("Hungarian forint", "ハンガリー・フォリント|ハンガリーフォリント|フォリント") + .put("Iranian rial", "イラン・リアル") + .put("Yemeni rial", "イエメン・リアル") + .put("Israeli new shekel", "₪|ils|イスラエル・新シェケル|イスラエル新シェケル") + .put("Japanese yen", "円") + .put("Sen", "銭") + .put("Kazakhstani tenge", "テンゲ|カザフスタン・テンゲ|カザフスタンテンゲ") + .put("Kenyan shilling", "ケニア・シリング") + .put("North Korean won", "北朝鮮ウォン") + .put("South Korean won", "韓国ウォン") + .put("Korean won", "₩") + .put("Kyrgyzstani som", "キルギス・ソム|ソム") + .put("Lao kip", "キップ|ラオス・キップ|ラオスキップ") + .put("Att", "att") + .put("Lesotho loti", "ロチ|レソト・ロチ|レソトロチ") + .put("South African rand", "ランド|南アフリカ・ランド|南アフリカランド") + .put("Macedonian denar", "マケドニア・デナール") + .put("Deni", "デニ") + .put("Malagasy ariary", "アリアリ|マダガスカル・アリアリ|マダガスカルアリアリ") + .put("Iraimbilanja", "イライムビランジャ") + .put("Malawian kwacha", "マラウイ・クワチャ") + .put("Tambala", "タンバラ") + .put("Malaysian ringgit", "リンギット|マレーシア・リンギット") + .put("Mauritanian ouguiya", "ウギア|モーリタニア・ウギア|モーリタニアウギア") + .put("Khoums", "コウム") + .put("Mozambican metical", "メティカル|モザンビーク・メティカル|モザンビークメティカル") + .put("Burmese kyat", "チャット|ミャンマー・チャット|ミャンマーチャット") + .put("Pya", "ピャー") + .put("Nigerian naira", "ナイラ|ナイジェリア・ナイラ|ナイジェリアナイラ") + .put("Kobo", "コボ") + .put("Turkish lira", "トルコリラ") + .put("Kuruş", "クルシュ") + .put("Omani rial", "オマーン・リアル") + .put("Panamanian balboa", "バルボア|パナマ・バルボア|パナマバルボア") + .put("Centesimo", "センテシモ") + .put("Papua New Guinean kina", "キナ|パプア・ニューギニア・キナ") + .put("Toea", "トエア") + .put("Peruvian sol", "ヌエボ・ソル") + .put("Polish złoty", "ズウォティ|ポーランド・ズウォティ|ポーランドズウォティ") + .put("Grosz", "グロシュ") + .put("Qatari riyal", "カタール・リヤル") + .put("Saudi riyal", "サウジアラビア・リヤル") + .put("Riyal", "リヤル") + .put("Dirham", "ディルハム") + .put("Halala", "ハララ") + .put("Samoan tālā", "タラ|サモア・タラ|サモアタラ") + .put("Sierra Leonean leone", "レオン|シエラレオネ・レオン|シエラレオネレオン") + .put("Peseta", "ペセタ") + .put("Swazi lilangeni", "リランゲニ|スワジランド・リランゲニ|スワジランドリランゲニ") + .put("Tajikistani somoni", "ソモニ|タジキスタン・ソモニ|タジキスタンソモニ") + .put("Thai baht", "バーツ|タイ・バーツ|タイバーツ") + .put("Satang", "サタン") + .put("Tongan paʻanga", "パアンガ|トンガ・パアンガ|トンガパアンガ") + .put("Ukrainian hryvnia", "フリヴニャ|ウクライナ・フリヴニャ|ウクライナフリヴニャ") + .put("Vanuatu vatu", "バツ|バヌアツ・バツ|バヌアツバツ") + .put("Vietnamese dong", "ドン|ベトナム・ドン|ベトナムドン") + .put("Indonesian rupiah", "ルピア|インドネシア・ルピア|インドネシアルピア") + .put("Netherlands guilder", "オランダ・ユーロ") + .put("Surinam florin", "スリナムフロリン") + .put("Zambian kwacha", "ザンビア・クワチャ") + .put("Moroccan dirham", "モロッコ・ディルハム") + .put("United Arab Emirates dirham", "UAEディルハム") + .put("Azerbaijani manat", "アゼルバイジャン・マナト") + .put("Turkmenistan manat", "トルクメニスタン・マナト") + .put("Manat", "マナト") + .put("Somali shilling", "ソマリア・シリング") + .put("Somaliland shilling", "ソマリランド・シリング") + .put("Tanzanian shilling", "タンザニア・シリング") + .put("Ugandan shilling", "ウガンダ・シリング") + .put("Romanian leu", "ルーマニア・レウ") + .put("Moldovan leu", "モルドバ・レウ") + .put("Leu", "レウ") + .put("Ban", "バン") + .put("Nepalese rupee", "ネパール・ルピー") + .put("Pakistani rupee", "パキスタン・ルピー") + .put("Indian rupee", "インド・ルピー") + .put("Seychellois rupee", "セーシェル・ルピー") + .put("Mauritian rupee", "モーリシャス・ルピー") + .put("Maldivian rufiyaa", "ルフィヤ|モルディブ・ルフィヤ|モルディブルフィヤ") + .put("Sri Lankan rupee", "スリランカ・ルピー") + .put("Rupee", "ルピー") + .put("Czech koruna", "チェコ・コルナ") + .put("Danish krone", "デンマーク・クローネ") + .put("Norwegian krone", "ノルウェー・クローネ") + .put("Faroese króna", "フェロー・クローネ") + .put("Icelandic króna", "アイスランド・クローナ") + .put("Swedish krona", "スウェーデン・クローナ") + .put("Krone", "クローナ") + .put("Øre", "オーレ") + .put("West African CFA franc", "西アフリカCFAフラン") + .put("Central African CFA franc", "中央アフリカCFAフラン") + .put("Comorian franc", "コモロ・フラン") + .put("Congolese franc", "コンゴ・フラン") + .put("Burundian franc", "ブルンジ・フラン") + .put("Djiboutian franc", "ジブチ・フラン") + .put("CFP franc", "CFPフラン") + .put("Guinean franc", "ギニア・フラン") + .put("Swiss franc", "スイス・フラン") + .put("Rwandan franc", "ルワンダ・フラン") + .put("Belgian franc", "ベルギー・フラン") + .put("Rappen", "Rappen") + .put("Franc", "フラン") + .put("Centime", "サンチーム") + .put("Russian ruble", "ロシア・ルーブル") + .put("Transnistrian ruble", "沿ドニエストル・ルーブル") + .put("Belarusian ruble", "ベラルーシ・ルーブル") + .put("Kopek", "カペイカ") + .put("Ruble", "ルーブル") + .put("Algerian dinar", "アルジェリア・ディナール") + .put("Bahraini dinar", "バーレーン・ディナール") + .put("Iraqi dinar", "イラク・ディナール") + .put("Jordanian dinar", "ヨルダン・ディナール") + .put("Kuwaiti dinar", "クウェート・ディナール") + .put("Libyan dinar", "リビア・ディナール") + .put("Serbian dinar", "セルビア・ディナール") + .put("Tunisian dinar", "チュニジア・ディナール") + .put("Dinar", "ディナール") + .put("Fils", "フィルス") + .put("Para", "パラ") + .put("Millime", "ミリム") + .put("Argentine peso", "アルゼンチン・ペソ") + .put("Chilean peso", "チリ・ペソ") + .put("Colombian peso", "コロンビア・ペソ") + .put("Cuban peso", "兌換ペソ") + .put("Dominican peso", "ドミニカ・ペソ") + .put("Mexican peso", "メキシコ・ペソ") + .put("Philippine peso", "フィリピン・ペソ") + .put("Uruguayan peso", "ウルグアイ・ペソ") + .put("Peso", "ペソ") + .put("Centavo", "センターボ") + .put("Alderney pound", "オルダニーポンド") + .put("British pound", "UKポンド") + .put("Guernsey pound", "ガーンジー・ポンド") + .put("Saint Helena pound", "セントヘレナ・ポンド") + .put("Egyptian pound", "エジプト・ポンド") + .put("Falkland Islands pound", "フォークランド諸島ポンド") + .put("Gibraltar pound", "ジブラルタル・ポンド") + .put("Manx pound", "マン島ポンド") + .put("Jersey pound", "ジャージー・ポンド") + .put("Lebanese pound", "レバノン・ポンド") + .put("South Sudanese pound", "南スーダン・ポンド") + .put("Sudanese pound", "スーダン・ポンド") + .put("Syrian pound", "シリア・ポンド") + .put("Pound", "ポンド") + .put("Pence", "ペンス") + .put("Shilling", "シリング") + .put("United States dollar", "ドル|USドル") + .put("East Caribbean dollar", "東カリブ・ドル") + .put("Australian dollar", "オーストラリア・ドル|オーストラリアドル") + .put("Bahamian dollar", "バハマ・ドル") + .put("Barbadian dollar", "バルバドス・ドル") + .put("Belize dollar", "ベリーズ・ドル") + .put("Bermudian dollar", "バミューダ・ドル") + .put("Brunei dollar", "ブルネイ・ドル") + .put("Singapore dollar", "シンガポール・ドル") + .put("Canadian dollar", "カナダ・ドル") + .put("Cayman Islands dollar", "ケイマン諸島・ドル") + .put("New Zealand dollar", "ニュージーランド・ドル") + .put("Cook Islands dollar", "クックアイランド・ドル") + .put("Fijian dollar", "フィジー・ドル|フィジー・ドル") + .put("Guyanese dollar", "ガイアナ・ドル|ガイアナ・ドル") + .put("Hong Kong dollar", "香港ドル") + .put("Macau Pataca", "マカオ・パタカ|マカオ・パタカ") + .put("New Taiwan dollar", "ニュー台湾ドル|ニュー台湾ドル") + .put("Jamaican dollar", "ジャマイカ・ドル|ジャマイカドル") + .put("Kiribati dollar", "キリバス・ドル") + .put("Liberian dollar", "リベリア・ドル|リベリアドル") + .put("Namibian dollar", "ナミビア・ドル|ナミビアドル") + .put("Surinamese dollar", "スリナム・ドル|スリナムドル") + .put("Trinidad and Tobago dollar", "トリニダード・トバゴ・ドル|トリニダードトバゴ・ドル") + .put("Tuvaluan dollar", "ツバル・ドル|ツバルドル") + .put("Chinese yuan", "人民元") + .put("Fen", "分") + .put("Jiao", "角") + .put("Finnish markka", "フィンランド・マルカ") + .put("Penni", "ペニー") + .build(); + + public static final ImmutableMap CurrencyNameToIsoCodeMap = ImmutableMap.builder() + .put("Afghan afghani", "AFN") + .put("Euro", "EUR") + .put("Albanian lek", "ALL") + .put("Angolan kwanza", "AOA") + .put("Armenian dram", "AMD") + .put("Aruban florin", "AWG") + .put("Bangladeshi taka", "BDT") + .put("Bhutanese ngultrum", "BTN") + .put("Bolivian boliviano", "BOB") + .put("Bosnia and Herzegovina convertible mark", "BAM") + .put("Botswana pula", "BWP") + .put("Brazilian real", "BRL") + .put("Bulgarian lev", "BGN") + .put("Cambodian riel", "KHR") + .put("Cape Verdean escudo", "CVE") + .put("Costa Rican colón", "CRC") + .put("Croatian kuna", "HRK") + .put("Czech koruna", "CZK") + .put("Eritrean nakfa", "ERN") + .put("Ethiopian birr", "ETB") + .put("Gambian dalasi", "GMD") + .put("Georgian lari", "GEL") + .put("Ghanaian cedi", "GHS") + .put("Guatemalan quetzal", "GTQ") + .put("Haitian gourde", "HTG") + .put("Honduran lempira", "HNL") + .put("Hungarian forint", "HUF") + .put("Iranian rial", "IRR") + .put("Yemeni rial", "YER") + .put("Israeli new shekel", "ILS") + .put("Japanese yen", "JPY") + .put("Kazakhstani tenge", "KZT") + .put("Kenyan shilling", "KES") + .put("North Korean won", "KPW") + .put("South Korean won", "KRW") + .put("Kyrgyzstani som", "KGS") + .put("Lao kip", "LAK") + .put("Lesotho loti", "LSL") + .put("South African rand", "ZAR") + .put("Macanese pataca", "MOP") + .put("Macedonian denar", "MKD") + .put("Malagasy ariary", "MGA") + .put("Malawian kwacha", "MWK") + .put("Malaysian ringgit", "MYR") + .put("Mauritanian ouguiya", "MRO") + .put("Mongolian tögrög", "MNT") + .put("Mozambican metical", "MZN") + .put("Burmese kyat", "MMK") + .put("Nicaraguan córdoba", "NIO") + .put("Nigerian naira", "NGN") + .put("Turkish lira", "TRY") + .put("Omani rial", "OMR") + .put("Panamanian balboa", "PAB") + .put("Papua New Guinean kina", "PGK") + .put("Paraguayan guaraní", "PYG") + .put("Peruvian sol", "PEN") + .put("Polish złoty", "PLN") + .put("Qatari riyal", "QAR") + .put("Saudi riyal", "SAR") + .put("Samoan tālā", "WST") + .put("São Tomé and Príncipe dobra", "STD") + .put("Sierra Leonean leone", "SLL") + .put("Swazi lilangeni", "SZL") + .put("Tajikistani somoni", "TJS") + .put("Thai baht", "THB") + .put("Ukrainian hryvnia", "UAH") + .put("Vanuatu vatu", "VUV") + .put("Venezuelan bolívar", "VEF") + .put("Zambian kwacha", "ZMW") + .put("Moroccan dirham", "MAD") + .put("United Arab Emirates dirham", "AED") + .put("Azerbaijani manat", "AZN") + .put("Turkmenistan manat", "TMT") + .put("Somali shilling", "SOS") + .put("Tanzanian shilling", "TZS") + .put("Ugandan shilling", "UGX") + .put("Romanian leu", "RON") + .put("Moldovan leu", "MDL") + .put("Nepalese rupee", "NPR") + .put("Pakistani rupee", "PKR") + .put("Indian rupee", "INR") + .put("Seychellois rupee", "SCR") + .put("Mauritian rupee", "MUR") + .put("Maldivian rufiyaa", "MVR") + .put("Sri Lankan rupee", "LKR") + .put("Indonesian rupiah", "IDR") + .put("Danish krone", "DKK") + .put("Norwegian krone", "NOK") + .put("Icelandic króna", "ISK") + .put("Swedish krona", "SEK") + .put("West African CFA franc", "XOF") + .put("Central African CFA franc", "XAF") + .put("Comorian franc", "KMF") + .put("Congolese franc", "CDF") + .put("Burundian franc", "BIF") + .put("Djiboutian franc", "DJF") + .put("CFP franc", "XPF") + .put("Guinean franc", "GNF") + .put("Swiss franc", "CHF") + .put("Rwandan franc", "RWF") + .put("Russian ruble", "RUB") + .put("Transnistrian ruble", "PRB") + .put("Belarusian ruble", "BYN") + .put("Algerian dinar", "DZD") + .put("Bahraini dinar", "BHD") + .put("Iraqi dinar", "IQD") + .put("Jordanian dinar", "JOD") + .put("Kuwaiti dinar", "KWD") + .put("Libyan dinar", "LYD") + .put("Serbian dinar", "RSD") + .put("Tunisian dinar", "TND") + .put("Argentine peso", "ARS") + .put("Chilean peso", "CLP") + .put("Colombian peso", "COP") + .put("Cuban convertible peso", "CUC") + .put("Cuban peso", "CUP") + .put("Dominican peso", "DOP") + .put("Mexican peso", "MXN") + .put("Uruguayan peso", "UYU") + .put("British pound", "GBP") + .put("Saint Helena pound", "SHP") + .put("Egyptian pound", "EGP") + .put("Falkland Islands pound", "FKP") + .put("Gibraltar pound", "GIP") + .put("Manx pound", "IMP") + .put("Jersey pound", "JEP") + .put("Lebanese pound", "LBP") + .put("South Sudanese pound", "SSP") + .put("Sudanese pound", "SDG") + .put("Syrian pound", "SYP") + .put("United States dollar", "USD") + .put("Australian dollar", "AUD") + .put("Bahamian dollar", "BSD") + .put("Barbadian dollar", "BBD") + .put("Belize dollar", "BZD") + .put("Bermudian dollar", "BMD") + .put("Brunei dollar", "BND") + .put("Singapore dollar", "SGD") + .put("Canadian dollar", "CAD") + .put("Cayman Islands dollar", "KYD") + .put("New Zealand dollar", "NZD") + .put("Fijian dollar", "FJD") + .put("Guyanese dollar", "GYD") + .put("Hong Kong dollar", "HKD") + .put("Jamaican dollar", "JMD") + .put("Liberian dollar", "LRD") + .put("Namibian dollar", "NAD") + .put("Solomon Islands dollar", "SBD") + .put("Surinamese dollar", "SRD") + .put("New Taiwan dollar", "TWD") + .put("Trinidad and Tobago dollar", "TTD") + .put("Tuvaluan dollar", "TVD") + .put("Chinese yuan", "CNY") + .put("Rial", "__RI") + .put("Shiling", "__S") + .put("Som", "__SO") + .put("Dirham", "__DR") + .put("Dinar", "_DN") + .put("Dollar", "__D") + .put("Manat", "__MA") + .put("Rupee", "__R") + .put("Krone", "__K") + .put("Krona", "__K") + .put("Crown", "__K") + .put("Frank", "__F") + .put("Mark", "__M") + .put("Ruble", "__RB") + .put("Peso", "__PE") + .put("Pound", "__P") + .put("Tristan da Cunha pound", "_TP") + .put("South Georgia and the South Sandwich Islands pound", "_SP") + .put("Somaliland shilling", "_SS") + .put("Pitcairn Islands dollar", "_PND") + .put("Palauan dollar", "_PD") + .put("Niue dollar", "_NID") + .put("Nauruan dollar", "_ND") + .put("Micronesian dollar", "_MD") + .put("Kiribati dollar", "_KID") + .put("Guernsey pound", "_GGP") + .put("Faroese króna", "_FOK") + .put("Cook Islands dollar", "_CKD") + .put("British Virgin Islands dollar", "_BD") + .put("Ascension pound", "_AP") + .put("Alderney pound", "_ALP") + .put("Abkhazian apsar", "_AA") + .build(); + + public static final ImmutableMap FractionalUnitNameToCodeMap = ImmutableMap.builder() + .put("Jiao", "JIAO") + .put("Kopek", "KOPEK") + .put("Pul", "PUL") + .put("Cent", "CENT") + .put("Qindarkë", "QINDARKE") + .put("Penny", "PENNY") + .put("Santeem", "SANTEEM") + .put("Cêntimo", "CENTIMO") + .put("Centavo", "CENTAVO") + .put("Luma", "LUMA") + .put("Qəpik", "QƏPIK") + .put("Fils", "FILS") + .put("Poisha", "POISHA") + .put("Kapyeyka", "KAPYEYKA") + .put("Centime", "CENTIME") + .put("Chetrum", "CHETRUM") + .put("Paisa", "PAISA") + .put("Fening", "FENING") + .put("Thebe", "THEBE") + .put("Sen", "SEN") + .put("Stotinka", "STOTINKA") + .put("Fen", "FEN") + .put("Céntimo", "CENTIMO") + .put("Lipa", "LIPA") + .put("Haléř", "HALER") + .put("Øre", "ØRE") + .put("Piastre", "PIASTRE") + .put("Santim", "SANTIM") + .put("Oyra", "OYRA") + .put("Butut", "BUTUT") + .put("Tetri", "TETRI") + .put("Pesewa", "PESEWA") + .put("Fillér", "FILLER") + .put("Eyrir", "EYRIR") + .put("Dinar", "DINAR") + .put("Agora", "AGORA") + .put("Tïın", "TIIN") + .put("Chon", "CHON") + .put("Jeon", "JEON") + .put("Tyiyn", "TYIYN") + .put("Att", "ATT") + .put("Sente", "SENTE") + .put("Dirham", "DIRHAM") + .put("Rappen", "RAPPEN") + .put("Avo", "AVO") + .put("Deni", "DENI") + .put("Iraimbilanja", "IRAIMBILANJA") + .put("Tambala", "TAMBALA") + .put("Laari", "LAARI") + .put("Khoums", "KHOUMS") + .put("Ban", "BAN") + .put("Möngö", "MONGO") + .put("Pya", "PYA") + .put("Kobo", "KOBO") + .put("Kuruş", "KURUS") + .put("Baisa", "BAISA") + .put("Centésimo", "CENTESIMO") + .put("Toea", "TOEA") + .put("Sentimo", "SENTIMO") + .put("Grosz", "GROSZ") + .put("Sene", "SENE") + .put("Halala", "HALALA") + .put("Para", "PARA") + .put("Öre", "ORE") + .put("Diram", "DIRAM") + .put("Satang", "SATANG") + .put("Seniti", "SENITI") + .put("Millime", "MILLIME") + .put("Tennesi", "TENNESI") + .put("Kopiyka", "KOPIYKA") + .put("Tiyin", "TIYIN") + .put("Hào", "HAO") + .put("Ngwee", "NGWEE") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?と)"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("United States dollar", "us$") + .put("British Virgin Islands dollar", "bvi$") + .put("Brunei dollar", "b$") + .put("Sen", "sen") + .put("Singapore dollar", "s$") + .put("Canadian dollar", "can$|c$|c $") + .put("Cayman Islands dollar", "ci$") + .put("New Zealand dollar", "nz$|nz $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hk$|hkd|hk $") + .put("Jamaican dollar", "j$") + .put("Namibian dollar", "nad|n$|n $") + .put("Solomon Islands dollar", "si$|si $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥|\\") + .put("Turkish lira", "₺") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .build(); + + public static final List CurrencyAmbiguousValues = Arrays.asList("円", "銭", "分", "レク", "プル", "ブル", "\\"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java new file mode 100644 index 000000000..5e1863215 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java @@ -0,0 +1,510 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class PortugueseNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Ano", "anos|ano") + .put("Mês", "meses|mes|mês") + .put("Semana", "semanas|semana") + .put("Dia", "dias|dia") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("anos", "ano", "meses", "mes", "mês", "semanas", "semana", "dias", "dia"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Quilômetro quadrado", "quilômetro quadrado|quilómetro quadrado|quilometro quadrado|quilômetros quadrados|quilómetros quadrados|quilomeros quadrados|km2|km^2|km²") + .put("Hectare", "hectômetro quadrado|hectómetro quadrado|hectômetros quadrados|hectómetros cuadrados|hm2|hm^2|hm²|hectare|hectares") + .put("Decâmetro quadrado", "decâmetro quadrado|decametro quadrado|decâmetros quadrados|decametro quadrado|dam2|dam^2|dam²|are|ares") + .put("Metro quadrado", "metro quadrado|metros quadrados|m2|m^2|m²") + .put("Decímetro quadrado", "decímetro quadrado|decimentro quadrado|decímetros quadrados|decimentros quadrados|dm2|dm^2|dm²") + .put("Centímetro quadrado", "centímetro quadrado|centimetro quadrado|centímetros quadrados|centrimetros quadrados|cm2|cm^2|cm²") + .put("Milímetro quadrado", "milímetro quadrado|milimetro quadrado|milímetros quadrados|militmetros quadrados|mm2|mm^2|mm²") + .put("Polegada quadrada", "polegada quadrada|polegadas quadradas|in2|in^2|in²") + .put("Pé quadrado", "pé quadrado|pe quadrado|pés quadrados|pes quadrados|pé2|pé^2|pé²|sqft|sq ft|ft2|ft^2|ft²") + .put("Jarda quadrada", "jarda quadrada|jardas quadradas|yd2|yd^2|yd²") + .put("Milha quadrada", "milha quadrada|milhas quadradas|mi2|mi^2|mi²") + .put("Acre", "acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Dólar", "dólar|dolar|dólares|dolares") + .put("Peso", "peso|pesos") + .put("Coroa", "coroa|coroas") + .put("Rublo", "rublo|rublos") + .put("Libra", "libra|libras") + .put("Florim", "florim|florins|ƒ") + .put("Dinar", "dinar|dinares") + .put("Franco", "franco|francos") + .put("Rupia", "rúpia|rupia|rúpias|rupias") + .put("Escudo", "escudo|escudos") + .put("Xelim", "xelim|xelins|xelims") + .put("Lira", "lira|liras") + .put("Centavo", "centavo|cêntimo|centimo|centavos|cêntimos|centimo") + .put("Centésimo", "centésimo|centésimos") + .put("Pêni", "pêni|péni|peni|penies|pennies") + .put("Manat", "manat|manate|mánate|man|manats|manates|mánates") + .put("Euro", "euro|euros|€|eur") + .put("Centavo de Euro", "centavo de euro|cêntimo de euro|centimo de euro|centavos de euro|cêntimos de euro|centimos de euro") + .put("Dólar do Caribe Oriental", "dólar do Caribe Oriental|dolar do Caribe Oriental|dólares do Caribe Oriental|dolares do Caribe Oriental|dólar das Caraíbas Orientais|dolar das Caraibas Orientais|dólares das Caraíbas Orientais|dolares das Caraibas Orientais|ec$|xcd") + .put("Centavo do Caribe Oriental", "centavo do Caribe Oriental|centavo das Caraíbas Orientais|cêntimo do Caribe Oriental|cêntimo das Caraíbas Orientais|centavos do Caribe Oriental|centavos das Caraíbas Orientais|cêntimos do Caribe Oriental|cêntimos das Caraíbas Orientais") + .put("Franco CFA da África Ocidental", "franco CFA da África Ocidental|franco CFA da Africa Ocidental|francos CFA da África Occidental|francos CFA da Africa Occidental|franco CFA Ocidental|xof") + .put("Centavo de CFA da África Ocidental", "centavo de CFA da Africa Occidental|centavos de CFA da África Ocidental|cêntimo de CFA da Africa Occidental|cêntimos de CFA da África Ocidental") + .put("Franco CFA da África Central", "franco CFA da África Central|franco CFA da Africa Central|francos CFA da África Central|francos CFA da Africa Central|franco CFA central|xaf") + .put("Centavo de CFA da África Central", "centavo de CFA de África Central|centavos de CFA da África Central|cêntimo de CFA de África Central|cêntimos de CFA da África Central") + .put("Apsar abcásio", "apsar abcásio|apsar abecásio|apsar abcasio|apsar|apsares") + .put("Afegani afegão", "afegani afegão|afegane afegão|؋|afn|afegane|afgane|afegâni|afeganis|afeganes|afganes|afegânis") + .put("Pul", "pul|pules|puls") + .put("Lek albanês", "lek|lekë|lekes|lek albanês|leque|leques|all") + .put("Qindarke", "qindarka|qindarkë|qindarke|qindarkas") + .put("Kwanza angolano", "kwanza angolano|kwanzas angolanos|kwanza|kwanzas|aoa|kz") + .put("Cêntimo angolano", "cêntimo angolano") + .put("Florim das Antilhas Holandesas", "florim das antilhas holandesas|florim das antilhas neerlandesas|ang") + .put("Rial saudita", "rial saudita|riais sauditas|riyal saudita|riyals sauditas|riyal|riyals|sar") + .put("Halala saudita", "halala saudita|halala|hallalah") + .put("Dinar argelino", "dinar argelino|dinares argelinos|dzd") + .put("Cêntimo argelino", "centimo argelino|centimos argelinos|cêntimo argelino|cêntimos argelinos|centavo argelino|centavos argelinos") + .put("Peso argentino", "peso argentino|pesos argentinos|ar$|ars") + .put("Centavo argentino", "centavo argentino|centavos argentinos|ctvo.|ctvos.") + .put("Dram armênio", "dram armênio|dram armênios|dram arménio|dram arménios|dram armenio|dram armenios|dram|drame|drames|դր.") + .put("Luma armênio", "luma armênio|lumas armênios|luma arménio|lumas arménios|luma armenio|lumas armenios|luma|lumas") + .put("Florim arubano", "florín arubeño|florines arubeños|ƒ arubeños|aƒ|awg") + .put("Dólar australiano", "dólar australiano|dólares australianos|dolar australiano|dolares australianos|a$|aud") + .put("Centavo australiano", "centavo australiano|centavos australianos") + .put("Manat azeri", "manat azeri|manats azeris|azn|manat azerbaijanês|manat azerbaijano|manats azerbaijaneses|manats azerbaijanos") + .put("Qəpik azeri", "qəpik azeri|qəpik|qəpiks") + .put("Dólar bahamense", "dólar bahamense|dólares bahamense|dolar bahamense|dolares bahamense|dólar baamiano|dólares baamiano|dolar baamiano|dolares baamiano|b$|bsd") + .put("Centavo bahamense", "centavo bahamense|centavos bahamense") + .put("Dinar bareinita", "dinar bareinita|dinar baremita|dinares bareinitas|dinares baremitas|bhd") + .put("Fil bareinita", "fil bareinita|fil baremita|fils bareinitas|fils baremitas") + .put("Taka bengali", "taka bengali|takas bengalis|taca|tacas|taka|takas|bdt") + .put("Poisha bengali", "poisha bengali|poishas bengalis") + .put("Dólar de Barbados", "dólar de barbados|dólares de barbados|dolar de barbados|dolares de barbados|dólar dos barbados|dólares dos barbados|bbd") + .put("Centavo de Barbados", "centavo de barbados|centavos de barbados|centavo dos barbados|centavos dos barbados") + .put("Dólar de Belize", "dólar de belize|dólares de belize|dolar de belize|dolares de belize|dólar do belize|dólares do belize|dolar do belize|dolares do belize|bz$|bzd") + .put("Centavo de Belize", "centavo de belize|centavos de belize|cêntimo do belize|cêntimos do belize") + .put("Dólar bermudense", "dólar bermudense|dólares bermudenses|bd$|bmd") + .put("Centavo bermudense", "centavo bermudense|centavos bermudenses|cêntimo bermudense| cêntimos bermudenses") + .put("Rublo bielorrusso", "rublo bielorrusso|rublos bielorrussos|byr") + .put("Copeque bielorusso", "copeque bielorrusso|copeques bielorrussos|kopek bielorrusso|kopeks bielorrussos|kap") + .put("Quiate mianmarense", "quiate mianmarense|quiates mianmarenses|kyat mianmarense|kyates mianmarenses|quiate myanmarense|quiates myanmarenses|kyat myanmarense|kyates myanmarenses|quiate birmanês|quite birmanes|quiates birmaneses|kyat birmanês|kyat birmanes|kyates birmaneses|mmk") + .put("Pya mianmarense", "pya mianmarense|pyas mianmarenses|pya myanmarense|pyas myanmarenses|pya birmanês|pya birmanes|pyas birmaneses") + .put("Boliviano", "boliviano|bolivianos|bob|bs") + .put("Centavo Boliviano", "centavo boliviano|centavos bolivianos") + .put("Marco da Bósnia e Herzegovina", "marco conversível|marco conversivel|marco convertível|marco convertivel|marcos conversíveis|marcos conversiveis|marcos convertíveis|marcos convertivies|bam") + .put("Fening da Bósnia e Herzegovina", "fening conversível|fening conversivel|fening convertível|fening convertivel|fenings conversíveis|fenings conversiveis|fenings convertíveis|fenings convertiveis") + .put("Pula", "pula|pulas|bwp") + .put("Thebe", "thebe|thebes") + .put("Real brasileiro", "real brasileiro|real do brasil|real|reais brasileiros|reais do brasil|reais|r$|brl") + .put("Centavo brasileiro", "centavo de real|centavo brasileiro|centavos de real|centavos brasileiros") + .put("Dólar de Brunei", "dólar de brunei|dolar de brunei|dólar do brunei|dolar do brunei|dólares de brunéi|dolares de brunei|dólares do brunei|dolares do brunei|bnd") + .put("Sen de Brunei", "sen de brunei|sen do brunei|sens de brunei|sens do brunei") + .put("Lev búlgaro", "lev búlgaro|leve búlgaro|leves búlgaros|lev bulgaro|leve bulgaro|leves bulgaros|lv|bgn") + .put("Stotinka búlgaro", "stotinka búlgaro|stotinki búlgaros|stotinka bulgaro|stotinki bulgaros") + .put("Franco do Burundi", "franco do burundi|francos do burundi|fbu|fib") + .put("Centavo Burundi", "centavo burundi|cêntimo burundi|centimo burundi|centavos burundi|cêntimo burundi|centimo burundi") + .put("Ngultrum butanês", "ngultrum butanês|ngultrum butanes|ngúltrume butanês|ngultrume butanes|ngultrum butaneses|ngúltrumes butaneses|ngultrumes butaneses|btn") + .put("Chetrum butanês", "chetrum butanês|chetrum butanes|chetrum butaneses") + .put("Escudo cabo-verdiano", "escudo cabo-verdiano|escudos cabo-verdianos|cve") + .put("Riel cambojano", "riel cambojano|riéis cambojanos|rieis cambojanos|khr") + .put("Dólar canadense", "dólar canadense|dolar canadense|dólares canadenses|dolares canadenses|c$|cad") + .put("Centavo canadense", "centavo canadense|centavos canadenses") + .put("Peso chileno", "peso chileno|pesos chilenos|cpl") + .put("Yuan chinês", "yuan chinês|yuan chines|yuans chineses|yuan|yuans|renminbi|rmb|cny|¥") + .put("Peso colombiano", "peso colombiano|pesos colombianos|cop|col$") + .put("Centavo colombiano", "centavo colombiano|centavos colombianos") + .put("Franco comorense", "franco comorense|francos comorenses|kmf|₣") + .put("Franco congolês", "franco congolês|franco congoles|francos congoleses|cdf") + .put("Centavo congolês", "centavo congolês|centavo congoles|centavos congoleses|cêntimo congolês|centimo congoles|cêntimos congoleses|cêntimos congoleses") + .put("Won norte-coreano", "won norte-coreano|wŏn norte-coreano|won norte-coreanos|wŏn norte-coreanos|kpw") + .put("Chon norte-coreano", "chon norte-coreano|chŏn norte-coreano|chŏn norte-coreanos|chon norte-coreanos") + .put("Won sul-coreano", "wŏn sul-coreano|won sul-coreano|wŏnes sul-coreanos|wones sul-coreanos|krw") + .put("Jeon sul-coreano", "jeons sul-coreano|jeons sul-coreanos") + .put("Colón costarriquenho", "colón costarriquenho|colon costarriquenho|colons costarriquenho|colones costarriquenhos|crc") + .put("Kuna croata", "kuna croata|kunas croatas|hrk") + .put("Lipa croata", "lipa croata|lipas croatas") + .put("Peso cubano", "peso cubano|pesos cubanos|cup") + .put("Peso cubano convertível", "peso cubano conversível|pesos cubanos conversíveis|peso cubano conversivel|pesos cubanos conversiveis|peso cubano convertível|pesos cubanos convertíveis|peso cubano convertivel|pesos cubanos convertiveis|cuc") + .put("Coroa dinamarquesa", "coroa dinamarquesa|coroas dinamarquesas|dkk") + .put("Libra egípcia", "libra egípcia|libra egipcia|libras egípcias|libras egipcias|egp|l.e.") + .put("Piastra egípcia", "piastra egípcia|piastra egipcia|pisastras egípcias|piastras egipcias") + .put("Dirham dos Emirados Árabes Unidos", "dirham|dirhams|dirham dos emirados arabes unidos|aed|dhs") + .put("Nakfa", "nakfa|nfk|ern") + .put("Centavo de Nakfa", "cêntimo de nakfa|cêntimos de nakfa|centavo de nafka|centavos de nafka") + .put("Peseta", "peseta|pesetas|pts.|ptas.|esp") + .put("Dólar estadunidense", "dólar dos estados unidos|dolar dos estados unidos|dólar estadunidense|dólar americano|dólares dos estados unidos|dolares dos estados unidos|dólares estadunidenses|dólares americanos|dolar estadunidense|dolar americano|dolares estadunidenses|dolares americanos|usd|u$d|us$|usd$") + .put("Coroa estoniana", "coroa estoniana|coroas estonianas|eek") + .put("Senti estoniano", "senti estoniano|senti estonianos") + .put("Birr etíope", "birr etíope|birr etiope|birr etíopes|birr etiopes|br|etb") + .put("Santim etíope", "santim etíope|santim etiope|santim etíopes|santim etiopes") + .put("Peso filipino", "peso filipino|pesos filipinos|php") + .put("Marco finlandês", "marco finlandês|marco finlandes|marcos finlandeses") + .put("Dólar fijiano", "dólar fijiano|dolar fijiano|dólares fijianos|dolares fijianos|fj$|fjd") + .put("Centavo fijiano", "centavo fijiano|centavos fijianos") + .put("Dalasi gambiano", "dalasi|gmd") + .put("Bututs", "butut|bututs") + .put("Lari georgiano", "lari georgiano|lari georgianos|gel") + .put("Tetri georgiano", "tetri georgiano|tetri georgianos") + .put("Cedi", "cedi|ghs|gh₵") + .put("Pesewa", "pesewa") + .put("Libra de Gibraltar", "libra de gibraltar|libras de gibraltar|gip") + .put("Peni de Gibraltar", "peni de gibraltar|penies de gibraltar") + .put("Quetzal guatemalteco", "quetzal guatemalteco|quetzales guatemaltecos|quetzal|quetzales|gtq") + .put("Centavo guatemalteco", "centavo guatemalteco|centavos guatemaltecos") + .put("Libra de Guernsey", "libra de Guernsey|libras de Guernsey|ggp") + .put("Peni de Guernsey", "peni de Guernsey|penies de Guernsey") + .put("Franco da Guiné", "franco da guiné|franco da guine| franco guineense|francos da guiné|francos da guine|francos guineense|gnf|fg") + .put("Centavo da Guiné", "cêntimo guineense|centimo guineense|centavo guineense|cêntimos guineenses|centimos guineenses|centavos guineenses") + .put("Dólar guianense", "dólar guianense|dólares guianense|dolar guianense|dolares guianense|gyd|gy") + .put("Gurde haitiano", "gurde haitiano|gourde|gurdes haitianos|htg") + .put("Centavo haitiano", "cêntimo haitiano|cêntimos haitianos|centavo haitiano|centavos haitianos") + .put("Lempira hondurenha", "lempira hondurenha|lempiras hondurenhas|lempira|lempiras|hnl") + .put("Centavo hondurenho", "centavo hondurenho|centavos hondurehos|cêntimo hondurenho|cêntimos hondurenhos") + .put("Dólar de Hong Kong", "dólar de hong kong|dolar de hong kong|dólares de hong kong|dolares de hong kong|hk$|hkd") + .put("Florim húngaro", "florim húngaro|florim hungaro|florins húngaros|florins hungaros|forinte|forintes|huf") + .put("Filér húngaro", "fillér|filér|filler|filer") + .put("Rupia indiana", "rúpia indiana|rupia indiana|rupias indianas|inr") + .put("Paisa indiana", "paisa indiana|paisas indianas") + .put("Rupia indonésia", "rupia indonesia|rupia indonésia|rupias indonesias|rupias indonésias|idr") + .put("Sen indonésio", "send indonésio|sen indonesio|sen indonésios|sen indonesios") + .put("Rial iraniano", "rial iraniano|riais iranianos|irr") + .put("Dinar iraquiano", "dinar iraquiano|dinares iraquianos|iqd") + .put("Fil iraquiano", "fil iraquiano|fils iraquianos|files iraquianos") + .put("Libra manesa", "libra manesa|libras manesas|imp") + .put("Peni manês", "peni manes|peni manês|penies maneses") + .put("Coroa islandesa", "coroa islandesa|coroas islandesas|isk|íkr") + .put("Aurar islandês", "aurar islandês|aurar islandes|aurar islandeses|eyrir") + .put("Dólar das Ilhas Cayman", "dólar das ilhas cayman|dolar das ilhas cayman|dólar das ilhas caimão|dólares das ilhas cayman|dolares das ilhas cayman|dólares das ilhas caimão|ci$|kyd") + .put("Dólar das Ilhas Cook", "dólar das ilhas cook|dolar das ilhas cook|dólares das ilhas cook|dolares das ilhas cook") + .put("Coroa feroesa", "coroa feroesa|coroas feroesas|fkr") + .put("Libra das Malvinas", "libra das malvinas|libras das malvinas|fk£|fkp") + .put("Dólar das Ilhas Salomão", "dólar das ilhas salomão|dolar das ilhas salomao|dólares das ilhas salomão|dolares das ilhas salomao|sbd") + .put("Novo shekel israelense", "novo shekel|novos shekeles|novo shequel|novo siclo|novo xéquel|shekeles novos|novos sheqalim|sheqalim novos|ils|₪") + .put("Agora", "agora|agorot") + .put("Dólar jamaicano", "dólar jamaicano|dolar jamaicano|dólares jamaicanos|dolares jamaicanos|j$|ja$|jmd") + .put("Yen", "yen|iene|yenes|ienes|jpy") + .put("Libra de Jersey", "libra de Jersey|libras de Jersey|jep") + .put("Dinar jordaniano", "dinar jordaniano|dinar jordano|dinares jordanianos|dinares jordanos|jd|jod") + .put("Piastra jordaniana", "piastra jordaniana|piastra jordano|piastras jordanianas|piastra jordaniano|piastras jordanianos|piastras jordanos") + .put("Tengue cazaque", "tenge|tengue|tengué|tengue cazaque|kzt") + .put("Tiyin", "tiyin|tiyins") + .put("Xelim queniano", "xelim queniano|xelins quenianos|ksh|kes") + .put("Som quirguiz", "som quirguiz|som quirguizes|soms quirguizes|kgs") + .put("Tyiyn", "tyiyn|tyiyns") + .put("Dólar de Kiribati", "dólar de kiribati|dolar de kiribati|dólares de kiribati|dolares de kiribati") + .put("Dinar kuwaitiano", "dinar kuwaitiano|dinar cuaitiano|dinares kuwaitiano|dinares cuaitianos|kwd") + .put("Quipe laosiano", "quipe|quipes|kipe|kipes|kip|kip laosiano|kip laociano|kips laosianos|kips laocianos|lak") + .put("Att laosiano", "at|att|att laosiano|att laosianos") + .put("Loti do Lesoto", "loti|lóti|maloti|lotis|lótis|lsl") + .put("Sente", "sente|lisente") + .put("Libra libanesa", "libra libanesa|libras libanesas|lbp") + .put("Dólar liberiano", "dólar liberiano|dolar liberiano|dólares liberianos|dolares liberianos|l$|lrd") + .put("Dinar libio", "dinar libio|dinar líbio|dinares libios|dinares líbios|ld|lyd") + .put("Dirham libio", "dirham libio|dirhams libios|dirham líbio|dirhams líbios") + .put("Litas lituana", "litas lituana|litai lituanas|ltl") + .put("Pataca macaense", "pataca macaense|patacas macaenses|mop$|mop") + .put("Avo macaense", "avo macaense|avos macaenses") + .put("Ho macaense", "ho macaense|ho macaenses") + .put("Dinar macedônio", "denar macedonio|denare macedonios|denar macedônio|denar macedónio|denare macedônio|denare macedónio|dinar macedonio|dinar macedônio|dinar macedónio|dinares macedonios|dinares macedônios|dinares macedónios|den|mkd") + .put("Deni macedônio", "deni macedonio|deni macedônio|deni macedónio|denis macedonios|denis macedônios|denis macedónios") + .put("Ariary malgaxe", "ariai malgaxe|ariary malgaxe|ariary malgaxes|ariaris|mga") + .put("Iraimbilanja", "iraimbilanja|iraimbilanjas") + .put("Ringuite malaio", "ringgit malaio|ringgit malaios|ringgits malaios|ringuite malaio|ringuites malaios|rm|myr") + .put("Sen malaio", "sen malaio|sen malaios|centavo malaio|centavos malaios|cêntimo malaio|cêntimos malaios") + .put("Kwacha do Malawi", "kwacha|cuacha|quacha|mk|mwk") + .put("Tambala", "tambala|tambalas|tambala malawi") + .put("Rupia maldiva", "rupia maldiva|rupias maldivas|rupia das maldivas| rupias das maldivas|mvr") + .put("Dirame marroquino", "dirame marroquino|dirham marroquinho|dirhams marroquinos|dirames marroquinos|mad") + .put("Rupia maurícia", "rupia maurícia|rupia de Maurício|rupia mauricia|rupia de mauricio|rupias de mauricio|rupias de maurício|rupias mauricias|rupias maurícias|mur") + .put("Uguia", "uguia|uguias|oguia|ouguiya|oguias|mro") + .put("Kume", "kumes|kume|khoums") + .put("Peso mexicano", "peso mexicano|pesos mexicanos|mxn") + .put("Centavo mexicano", "centavo mexicano|centavos mexicanos") + .put("Leu moldávio", "leu moldavo|lei moldavos|leu moldávio|leu moldavio|lei moldávios|lei moldavios|leus moldavos|leus moldavios|leus moldávios|mdl") + .put("Ban moldávio", "ban moldavo|bani moldavos") + .put("Tugrik mongol", "tugrik mongol|tugrik|tugriks mongóis|tugriks mongois|tug|mnt") + .put("Metical moçambicao", "metical|metical moçambicano|metical mocambicano|meticais|meticais moçambicanos|meticais mocambicanos|mtn|mzn") + .put("Dólar namibiano", "dólar namibiano|dólares namibianos|dolar namibio|dolares namibios|n$|nad") + .put("Centavo namibiano", "centavo namibiano|centavos namibianos|centavo namibio|centavos namibianos") + .put("Rupia nepalesa", "rupia nepalesa|rupias nepalesas|npr") + .put("Paisa nepalesa", "paisa nepalesa|paisas nepalesas") + .put("Córdova nicaraguense", "córdova nicaraguense|cordova nicaraguense|cordova nicaraguana|córdoba nicaragüense|córdobas nicaragüenses|cordobas nicaraguenses|córdovas nicaraguenses|cordovas nicaraguenses|córdovas nicaraguanasc$|nio") + .put("Centavo nicaraguense", "centavo nicaragüense|centavos nicaraguenses|centavo nicaraguano|centavos nicaraguenses|centavo nicaraguano|centavos nicaraguanos") + .put("Naira", "naira|ngn") + .put("Kobo", "kobo") + .put("Coroa norueguesa", "coroa norueguesa|coroas norueguesas|nok") + .put("Franco CFP", "franco cfp|francos cfp|xpf") + .put("Dólar neozelandês", "dólar neozelandês|dolar neozelandes|dólares neozelandeses|dolares neozelandeses|dólar da nova zelândia|dolar da nova zelandia|dólares da nova zelândia|dolares da nova zelandia|nz$|nzd") + .put("Centavo neozelandês", "centavo neozelandês|centavo neozelandes|centavo da nova zelandia|centavo da nova zelândia|centavos da nova zelandia|centavos neozelandeses|centavos da nova zelândia") + .put("Rial omanense", "rial omani|riais omanis|rial omanense|riais omanenses|omr") + .put("Baisa omanense", "baisa omani|baisas omanis|baisa omanense|baisas omanenses") + .put("Florim holandês", "florim holandês|florim holandes|florins holandeses|nlg") + .put("Rupia paquistanesa", "rupia paquistanesa|rupias paquistanesas|pkr") + .put("Paisa paquistanesa", "paisa paquistanesa|paisas paquistanesasas") + .put("Balboa panamenho", "balboa panamenho|balboas panamenhos|balboa|pab|balboa panamense|balboas panamenses") + .put("Centavo panamenho", "centavo panamenho|cêntimo panamenho|centavos panamenhos|cêntimos panamenhos|cêntimo panamense|cêntimos panamenses") + .put("Kina", "kina|kina papuásia|kinas|kinas papuásias|pkg|pgk") + .put("Toea", "toea") + .put("Guarani", "guarani|guaranis|gs|pyg") + .put("Novo Sol", "novo sol peruano|novos sóis peruanos|sol|soles|sóis|nuevo sol|pen|s#.") + .put("Centavo de sol", "cêntimo de sol|cêntimos de sol|centavo de sol|centavos de sol") + .put("Złoty", "złoty|złotys|zloty|zlotys|zloti|zlotis|zlóti|zlótis|zlote|zł|pln") + .put("Groszy", "groszy|grosz") + .put("Rial catariano", "rial qatari|riais qataris|rial catarense|riais catarenses|rial catariano|riais catarianos|qr|qar") + .put("Dirame catariano", "dirame catariano|dirames catarianos|dirame qatari|dirames qataris|dirame catarense|dirames catarenses|dirham qatari|dirhams qataris|dirham catarense|dirhams catarenses|dirham catariano|dirhams catariano") + .put("Libra esterlina", "libra esterlina|libras esterlinas|gbp") + .put("Coroa checa", "coroa checa|coroas checas|kc|czk") + .put("Peso dominicano", "peso dominicano|pesos dominicanos|rd$|dop") + .put("Centavo dominicano", "centavo dominicano|centavos dominicanos") + .put("Franco ruandês", "franco ruandês|franco ruandes|francos ruandeses|rf|rwf") + .put("Céntimo ruandês", "cêntimo ruandês|centimo ruandes|centavo ruandês|centavo ruandes|cêntimos ruandeses|centimos ruandeses|centavos ruandeses") + .put("Leu romeno", "leu romeno|lei romenos|leus romenos|ron") + .put("Ban romeno", "ban romeno|bani romeno|bans romenos") + .put("Rublo russo", "rublo russo|rublos russos|rub|р.") + .put("Copeque ruso", "copeque russo|copeques russos|kopek ruso|kopeks rusos|copeque|copeques|kopek|kopeks") + .put("Tala samoano", "tala|tālā|talas|tala samonano|talas samoanos|ws$|sat|wst") + .put("Sene samoano", "sene") + .put("Libra de Santa Helena", "libra de santa helena|libras de santa helena|shp") + .put("Pêni de Santa Helena", "peni de santa helena|penies de santa helena") + .put("Dobra", "dobra|dobras|db|std") + .put("Dinar sérvio", "dinar sérvio|dinar servio|dinar serbio|dinares sérvios|dinares servios|dinares serbios|rsd") + .put("Para sérvio", "para sérvio|para servio|para serbio|paras sérvios|paras servios|paras serbios") + .put("Rupia seichelense", "rupia de seicheles|rupias de seicheles|rupia seichelense|rupias seichelenses|scr") + .put("Centavo seichelense", "centavo de seicheles|centavos de seicheles|centavo seichelense|centavos seichelenses") + .put("Leone serra-leonino", "leone|leones|leone serra-leonino|leones serra-leoninos|le|sll") + .put("Dólar de Cingapura", "dólar de singapura|dolar de singapura|dórar de cingapura|dolar de cingapura|dólares de singapura|dolares de singapura|dólares de cingapura|dolares de cingapura|sgb") + .put("Centavo de Cingapura", "centavo de singapura|centavos de singapura|centavo de cingapura|centavos de cingapura") + .put("Libra síria", "libra síria|libra siria|libras sírias|libras sirias|s£|syp") + .put("Piastra síria", "piastra siria|piastras sirias|piastra síria|piastras sírias") + .put("Xelim somali", "xelim somali|xelins somalis|xelim somaliano|xelins somalianos|sos") + .put("Centavo somali", "centavo somapli|centavos somalis|centavo somaliano|centavos somalianos") + .put("Xelim da Somalilândia", "xelim da somalilândia|xelins da somalilândia|xelim da somalilandia|xelins da somalilandia") + .put("Centavo da Somalilândia", "centavo da somalilândia|centavos da somalilândia|centavo da somalilandia|centavos da somalilandia") + .put("Rupia do Sri Lanka", "rupia do sri lanka|rupia do sri lanca|rupias do sri lanka|rupias do sri lanca|rupia cingalesa|rupias cingalesas|lkr") + .put("Lilangeni", "lilangeni|lilangenis|emalangeni|szl") + .put("Rand sul-africano", "rand|rand sul-africano|rands|rands sul-africanos|zar") + .put("Libra sudanesa", "libra sudanesa|libras sudanesas|sdg") + .put("Piastra sudanesa", "piastra sudanesa|piastras sudanesas") + .put("Libra sul-sudanesa", "libra sul-sudanesa|libras sul-sudanesas|ssp") + .put("Piastra sul-sudanesa", "piastra sul-sudanesa|piastras sul-sudanesas") + .put("Coroa sueca", "coroa sueca|coroas suecas|sek") + .put("Franco suíço", "franco suíço|franco suico|francos suíços|francos suicos|sfr|chf") + .put("Rappen suíço", "rappen suíço|rappen suico|rappens suíços|rappens suicos") + .put("Dólar surinamês", "dólar surinamês|dolar surinames|dólar do Suriname|dolar do Suriname|dólares surinameses|dolares surinameses|dólares do Suriname|dolares do Suriname|srd") + .put("Centavo surinamês", "centavo surinamês|centavo surinames|centavos surinameses") + .put("Baht tailandês", "baht tailandês|bath tailandes|baht tailandeses|thb") + .put("Satang tailandês", "satang tailandês|satang tailandes|satang tailandeses") + .put("Novo dólar taiwanês", "novo dólar taiwanês|novo dolar taiwanes|dólar taiwanês|dolar taiwanes|dólares taiwaneses|dolares taiwaneses|twd") + .put("Centavo taiwanês", "centavo taiwanês|centavo taiwanes|centavos taiwaneses") + .put("Xelim tanzaniano", "xelim tanzaniano|xelins tanzanianos|tzs") + .put("Centavo tanzaniano", "centavo tanzaniano|centavos tanzanianos") + .put("Somoni tajique", "somoni tajique|somoni|somonis tajiques|somonis|tjs") + .put("Diram tajique", "diram tajique|dirams tajiques|dirames tajiques") + .put("Paʻanga", "paanga|paangas|paʻanga|pa'anga|top") + .put("Seniti", "seniti") + .put("Rublo transdniestriano", "rublo transdniestriano|rublos transdniestriano") + .put("Copeque transdniestriano", "copeque transdniestriano|copeques transdniestriano") + .put("Dólar de Trinidade e Tobago", "dólar de trinidade e tobago|dólares trinidade e tobago|dolar de trinidade e tobago|dolares trinidade e tobago|dólar de trinidad e tobago|dólares trinidad e tobago|ttd") + .put("Centavo de Trinidade e Tobago", "centavo de trinidade e tobago|centavos de trinidade e tobago|centavo de trinidad e tobago|centavos de trinidad e tobago") + .put("Dinar tunisiano", "dinar tunisiano|dinares tunisianos|dinar tunisino|dinares tunisinos|tnd") + .put("Milim tunisiano", "milim tunisiano|milim tunesianos|millime tunisianos|millimes tunisianos|milim tunisino|milim tunisinos|millime tunisinos|millimes tunisinos") + .put("Lira turca", "lira turca|liras turcas|try") + .put("Kuruş turco", "kuruş turco|kuruş turcos") + .put("Manat turcomeno", "manat turcomeno|manats turcomenos|tmt") + .put("Tennesi turcomeno", "tennesi turcomeno|tennesis turcomenos|tenge turcomenos|tenges turcomenos") + .put("Dólar tuvaluano", "dólar tuvaluano|dolar tuvaluano|dólares tuvaluanos|dolares tuvaluanos") + .put("Centavo tuvaluano", "centavo tuvaluano|centavos tuvaluanos") + .put("Grívnia", "grívnia|grivnia|grívnias|grivnias|grivna|grivnas|uah") + .put("Copeque ucraniano", "kopiyka|copeque ucraniano|copeques ucranianos") + .put("Xelim ugandês", "xelim ugandês|xelim ugandes|xelins ugandeses|ugx") + .put("Centavo ugandês", "centavo ugandês|centavo ugandes|centavos ugandeses") + .put("Peso uruguaio", "peso uruguaio|pesos uruguayis|uyu") + .put("Centésimo uruguayo", "centésimo uruguaio|centesimo uruguaio|centésimos uruguaios|centesimos uruguaios") + .put("Som uzbeque", "som uzbeque|som uzbeques|soms uzbeques|somes uzbeques|som usbeque|som usbeques|soms usbeques|somes usbeques|uzs") + .put("Tiyin uzbeque", "tiyin uzbeque|tiyin uzbeques|tiyins uzbeques|tiyin usbeque|tiyin usbeques|tiyins usbeques") + .put("Vatu", "vatu|vatus|vuv") + .put("Bolívar forte venezuelano", "bolívar forte|bolivar forte|bolívar|bolivar|bolívares|bolivares|vef") + .put("Centavo de bolívar", "cêntimo de bolívar|cêntimos de bolívar|centavo de bolívar|centavo de bolivar|centavos de bolívar|centavos de bolivar") + .put("Dongue vietnamita", "dongue vietnamita|Đồng vietnamita|dong vietnamita|dongues vietnamitas|dongs vietnamitas|vnd") + .put("Hào vietnamita", "hào vietnamita|hao vietnamita|hào vietnamitas|hàos vietnamitas|haos vietnamitas") + .put("Rial iemenita", "rial iemenita|riais iemenitas|yer") + .put("Fils iemenita", "fils iemenita|fils iemenitas") + .put("Franco djibutiano", "franco djibutiano|francos djibutianos|franco jibutiano|francos jibutianos|djf") + .put("Dinar iugoslavo", "dinar iugoslavo|dinares iugoslavos|dinar jugoslavo|dinares jugoslavos|yud") + .put("Kwacha zambiano", "kwacha zambiano|kwacha zambianos|kwachas zambianos|zmw") + .put("Ngwee zambiano", "ngwee zambiano|ngwee zambianos|ngwees zambianos") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dólar", "$") + .put("Dólar estadunidense", "us$|u$d|usd$|usd") + .put("Dólar do Caribe Oriental", "ec$|xcd") + .put("Dólar australiano", "a$|aud") + .put("Dólar bahamense", "b$|bsd") + .put("Dólar de Barbados", "bds$|bbd") + .put("Dólar de Belize", "bz$|bzd") + .put("Dólar bermudense", "bd$|bmd") + .put("Dólar de Brunei", "brunéi $|bnd") + .put("Dólar de Cingapura", "s$|sgd") + .put("Dólar canadense", "c$|can$|cad") + .put("Dólar das Ilhas Cayman", "ci$|kyd") + .put("Dólar neozelandês", "nz$|nzd") + .put("Dólar fijiano", "fj$|fjd") + .put("Dólar guianense", "gy$|gyd") + .put("Dólar de Hong Kong", "hk$|hkd") + .put("Dólar jamaicano", "j$|ja$|jmd") + .put("Dólar liberiano", "l$|lrd") + .put("Dólar namibiano", "n$|nad") + .put("Dólar das Ilhas Salomão", "si$|sbd") + .put("Novo dólar taiwanês", "nt$|twd") + .put("Real brasileiro", "r$|brl") + .put("Guarani", "₲|gs.|pyg") + .put("Dólar de Trinidade e Tobago", "tt$|ttd") + .put("Yuan chinês", "¥|cny|rmb") + .put("Yen", "¥|jpy") + .put("Euro", "€|eur") + .put("Florim", "ƒ") + .put("Libra", "£") + .put("Colón costarriquenho", "₡") + .put("Lira turca", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("le", "agora"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("bit", "bit|bits") + .put("kilobit", "kilobit|kilobits|kb|kbit") + .put("megabit", "megabit|megabits|Mb|Mbit") + .put("gigabit", "gigabit|gigabits|Gb|Gbit") + .put("terabit", "terabit|terabits|Tb|Tbit") + .put("petabit", "petabit|petabits|Pb|Pbit") + .put("kibibit", "kibibit|kibibits|kib|kibit") + .put("mebibit", "mebibit|mebibits|Mib|Mibit") + .put("gibibit", "gibibit|gibibits|Gib|Gibit") + .put("tebibit", "tebibit|tebibits|Tib|Tibit") + .put("pebibit", "pebibit|pebibits|Pib|Pibit") + .put("byte", "byte|bytes") + .put("kilobyte", "kilobyte|kilobytes|kB|kByte") + .put("megabyte", "megabyte|megabytes|MB|MByte") + .put("gigabyte", "gigabyte|gigabytes|GB|GByte") + .put("terabyte", "terabyte|terabytes|TB|TByte") + .put("petabyte", "petabyte|petabytes|PB|PByte") + .put("kibibyte", "kibibyte|kibibytes|kiB|kiByte") + .put("mebibyte", "mebibyte|mebibytes|MiB|MiByte") + .put("gibibyte", "gibibyte|gibibytes|GiB|GiByte") + .put("tebibyte", "tebibyte|tebibytes|TiB|TiByte") + .put("pebibyte", "pebibyte|pebibytes|PiB|PiByte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("ton", "tonelada", "área", "area", "áreas", "areas", "milha", "milhas", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^|\\P{L}))"; + + public static final String BuildSuffix = "(?=(\\s|\\P{L}|$))"; + + public static final String ConnectorToken = "de"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Quilômetro", "km|quilometro|quilômetro|quilómetro|quilometros|quilômetros|quilómetros") + .put("Hectômetro", "hm|hectometro|hectômetro|hectómetro|hectometros|hectômetros|hectómetros") + .put("Decâmetro", "decametro|decâmetro|decámetro|decametros|decâmetro|decámetros|dam") + .put("Metro", "m|m.|metro|metros") + .put("Decímetro", "dm|decimetro|decímetro|decimetros|decímetros") + .put("Centímetro", "cm|centimetro|centímetro|centimetros|centimetros") + .put("Milímetro", "mm|milimetro|milímetro|milimetros|milímetros") + .put("Micrômetro", "µm|um|micrometro|micrômetro|micrómetro|micrometros|micrômetros|micrómetros|micron|mícron|microns|mícrons|micra") + .put("Nanômetro", "nm|nanometro|nanômetro|nanómetro|nanometros|nanômetros|nanómetros|milimicron|milimícron|milimicrons|milimícrons") + .put("Picômetro", "pm|picometro|picômetro|picómetro|picometros|picômetros|picómetros") + .put("Milha", "mi|milha|milhas") + .put("Jarda", "yd|jarda|jardas") + .put("Polegada", "polegada|polegadas|\"") + .put("Pé", "pé|pe|pés|pes|ft") + .put("Ano luz", "ano luz|anos luz|al") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("mi", "milha", "milhas", "\""); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Metro por segundo", "metro/segundo|m/s|metro por segundo|metros por segundo|metros por segundos") + .put("Quilômetro por hora", "km/h|quilômetro por hora|quilómetro por hora|quilometro por hora|quilômetros por hora|quilómetros por hora|quilometros por hora|quilômetro/hora|quilómetro/hora|quilometro/hora|quilômetros/hora|quilómetros/hora|quilometros/hora") + .put("Quilômetro por minuto", "km/min|quilômetro por minuto|quilómetro por minuto|quilometro por minuto|quilômetros por minuto|quilómetros por minuto|quilometros por minuto|quilômetro/minuto|quilómetro/minuto|quilometro/minuto|quilômetros/minuto|quilómetros/minuto|quilometros/minuto") + .put("Quilômetro por segundo", "km/seg|quilômetro por segundo|quilómetro por segundo|quilometro por segundo|quilômetros por segundo|quilómetros por segundo|quilometros por segundo|quilômetro/segundo|quilómetro/segundo|quilometro/segundo|quilômetros/segundo|quilómetros/segundo|quilometros/segundo") + .put("Milha por hora", "mph|milha por hora|mi/h|milha/hora|milhas/hora|milhas por hora") + .put("Nó", "kt|nó|nós|kn") + .put("Pé por segundo", "ft/s|pé/s|pe/s|ft/seg|pé/seg|pe/seg|pé por segundo|pe por segundo|pés por segundo|pes por segundo") + .put("Pé por minuto", "ft/min|pé/mind|pe/min|pé por minuto|pe por minuto|pés por minuto|pes por minuto") + .put("Jarda por minuto", "jardas por minuto|jardas/minuto|jardas/min") + .put("Jarda por segundo", "jardas por segundo|jardas/segundo|jardas/seg") + .build(); + + public static final List AmbiguousSpeedUnitList = Arrays.asList("nó", "no", "nós", "nos"); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("Kelvin", "k|K|kelvin") + .put("Grau Rankine", "r|°r|°ra|grau rankine|graus rankine| rankine") + .put("Grau Celsius", "°c|grau c|grau celsius|graus c|graus celsius|celsius|grau centígrado|grau centrigrado|graus centígrados|graus centigrados|centígrado|centígrados|centigrado|centigrados") + .put("Grau Fahrenheit", "°f|grau f|graus f|grau fahrenheit|graus fahrenheit|fahrenheit") + .put("Grau", "°|graus|grau") + .build(); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Quilômetro cúbico", "quilômetro cúbico|quilómetro cúbico|quilometro cubico|quilômetros cúbicos|quilómetros cúbicos|quilometros cubicos|km3|km^3|km³") + .put("Hectômetro cúbico", "hectômetro cúbico|hectómetro cúbico|hectometro cubico|hectômetros cúbicos|hectómetros cúbicos|hectometros cubicos|hm3|hm^3|hm³") + .put("Decâmetro cúbico", "decâmetro cúbico|decámetro cúbico|decametro cubico|decâmetros cúbicos|decámetros cúbicos|decametros cubicosdam3|dam^3|dam³") + .put("Metro cúbico", "metro cúbico|metro cubico|metros cúbicos|metros cubicos|m3|m^3|m³") + .put("Decímetro cúbico", "decímetro cúbico|decimetro cubico|decímetros cúbicos|decimetros cubicos|dm3|dm^3|dm³") + .put("Centímetro cúbico", "centímetro cúbico|centimetro cubico|centímetros cúbicos|centrimetros cubicos|cc|cm3|cm^3|cm³") + .put("Milímetro cúbico", "milímetro cúbico|milimetro cubico|milímetros cúbicos|milimetros cubicos|mm3|mm^3|mm³") + .put("Polegada cúbica", "polegada cúbica|polegada cubica|polegadas cúbicas|polegadas cubicas") + .put("Pé cúbico", "pé cúbico|pe cubico|pés cúbicos|pes cubicos|pé3|pe3|pé^3|pe^3|pé³|pe³|ft3|ft^3|ft³") + .put("Jarda cúbica", "jarda cúbica|jarda cubica|jardas cúbicas|jardas cubicas|yd3|yd^3|yd³") + .put("Hectolitro", "hectolitro|hectolitros|hl") + .put("Litro", "litro|litros|lts|l") + .put("Mililitro", "mililitro|mililitros|ml") + .put("Galão", "galão|galões|galao|galoes") + .put("Pint", "pinta|pintas|pinto|pintos|quartilho|quartilhos|pint|pints") + .put("Barril", "barril|barris|bbl") + .put("Onça líquida", "onça líquida|onca liquida|onças líquidas|oncas liquidas") + .build(); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Tonelada métrica", "tonelada métrica|tonelada metrica|toneladas métricas|toneladas metricas") + .put("Tonelada", "ton|tonelada|toneladas") + .put("Quilograma", "kg|quilograma|quilogramas|quilo|quilos|kilo|kilos") + .put("Hectograma", "hg|hectograma|hectogramas") + .put("Decagrama", "dag|decagrama|decagramas") + .put("Grama", "g|grama|gramas") + .put("Decigrama", "dg|decigrama|decigramas") + .put("Centigrama", "cg|centigrama|centigramas") + .put("Miligrama", "mg|miligrama|miligramas") + .put("Micrograma", "µg|ug|micrograma|microgramas") + .put("Nanograma", "ng|nanograma|nanogramas") + .put("Picograma", "pg|picograma|picogramas") + .put("Libra", "lb|libra|libras") + .put("Onça", "oz|onça|onca|onças|oncas") + .put("Grão", "grão|grao|grãos|graos|gr") + .put("Quilate", "ct|quilate|quilates") + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java new file mode 100644 index 000000000..198cdea86 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java @@ -0,0 +1,517 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class SpanishNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Año", "años|año") + .put("Mes", "meses|mes") + .put("Semana", "semanas|semana") + .put("Día", "dias|días|día|dia") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("años", "año", "meses", "mes", "semanas", "semana", "dias", "días", "día", "dia"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Kilómetro cuadrado", "kilómetro cuadrado|kilómetros cuadrados|km2|km^2|km²") + .put("Hectómetro cuadrado", "hectómetro cuadrado|hectómetros cuadrados|hm2|hm^2|hm²|hectárea|hectáreas") + .put("Decámetro cuadrado", "decámetro cuadrado|decámetros cuadrados|dam2|dam^2|dam²|área|áreas") + .put("Metro cuadrado", "metro cuadrado|metros cuadrados|m2|m^2|m²") + .put("Decímetro cuadrado", "decímetro cuadrado|decímetros cuadrados|dm2|dm^2|dm²") + .put("Centímetro cuadrado", "centímetro cuadrado|centímetros cuadrados|cm2|cm^2|cm²") + .put("Milímetro cuadrado", "milímetro cuadrado|milímetros cuadrados|mm2|mm^2|mm²") + .put("Pulgada cuadrado", "pulgada cuadrada|pulgadas cuadradas") + .put("Pie cuadrado", "pie cuadrado|pies cuadrados|pie2|pie^2|pie²|ft2|ft^2|ft²") + .put("Yarda cuadrado", "yarda cuadrada|yardas cuadradas|yd2|yd^2|yd²") + .put("Acre", "acre|acres") + .build(); + + public static final List AreaAmbiguousValues = Arrays.asList("área", "áreas"); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Dólar", "dólar|dólares") + .put("Peso", "peso|pesos") + .put("Rublo", "rublo|rublos") + .put("Libra", "libra|libras") + .put("Florín", "florín|florines") + .put("Dinar", "dinar|dinares") + .put("Franco", "franco|francos") + .put("Rupia", "rupia|rupias") + .put("Escudo", "escudo|escudos") + .put("Chelín", "chelín|chelines") + .put("Lira", "lira|liras") + .put("Centavo", "centavo|centavos") + .put("Céntimo", "céntimo|céntimos") + .put("Centésimo", "centésimo|centésimos") + .put("Penique", "penique|peniques") + .put("Euro", "euro|euros|€|eur") + .put("Céntimo de Euro", "céntimo de euro|céntimos de euros") + .put("Dólar del Caribe Oriental", "dólar del caribe oriental|dólares del caribe oriental|ec$|xcd") + .put("Centavo del Caribe Oriental", "centavo del caribe oriental|centavos del caribe oriental") + .put("Franco CFA de África Occidental", "franco cfa de África occidental|francos cfa de África occidental|fcfa|xof") + .put("Céntimo de CFA de África Occidental", "céntimo de cfa de África occidental|céntimos de cfa de África occidental") + .put("Franco CFA de África Central", "franco cfa de África central|francos cfa de África central|xaf") + .put("Céntimo de CFA de África Central", "céntimo de cfa de África central|céntimos de cfa de África central") + .put("Apsar", "apsar|apsares") + .put("Afgani afgano", "afgani afgano|؋|afn|afganis|afgani") + .put("Pul", "pul|puls") + .put("Lek albanés", "lek|lekë|lekes|lek albanés") + .put("Qindarka", "qindarka|qindarkë|qindarkas") + .put("Kwanza angoleño", "kwanza angoleño|kwanzas angoleños|kwanza angoleños|kwanzas angoleño|kwanzas|aoa|kz") + .put("Cêntimo angoleño", "cêntimo angoleño|cêntimo|cêntimos") + .put("Florín antillano neerlandés", "florín antillano neerlandés|florínes antillano neerlandés|ƒ antillano neerlandés|ang|naƒ") + .put("Cent antillano neerlandés", "cent|centen") + .put("Riyal saudí", "riyal saudí|riyales saudí|sar") + .put("Halalá saudí", "halalá saudí|hallalah") + .put("Dinar argelino", "dinar argelino|dinares argelinos|dzd") + .put("Céntimo argelino", "centimo argelino|centimos argelinos") + .put("Peso argentino", "peso argentino|pesos argentinos|ar$|ars") + .put("Centavo argentino", "centavo argentino|centavos argentinos|ctvo.|ctvos.") + .put("Dram armenio", "dram armenio|dram armenios|dram|դր.") + .put("Luma armenio", "luma armenio|luma armenios") + .put("Florín arubeño", "florín arubeño|florines arubeños|ƒ arubeños|aƒ|awg") + .put("Yotin arubeño", "yotin arubeño|yotines arubeños") + .put("Dólar australiano", "dólar australiano|dólares australianos|a$|aud") + .put("Centavo australiano", "centavo australiano|centavos australianos") + .put("Manat azerí", "manat azerí|man|azn") + .put("Qəpik azerí", "qəpik azerí|qəpik") + .put("Dólar bahameño", "dólar bahameño|dólares bahameños|b$|bsd") + .put("Centavo bahameño", "centavo bahameño|centavos bahameños") + .put("Dinar bahreiní", "dinar bahreiní|dinares bahreinies|bhd") + .put("Fil bahreiní", "fil bahreiní|fils bahreinies") + .put("Taka bangladeshí", "taka bangladeshí|takas bangladeshí|bdt") + .put("Poisha bangladeshí", "poisha bangladeshí|poishas bangladeshí") + .put("Dólar de Barbados", "dólar de barbados|dólares de barbados|bbd") + .put("Centavo de Barbados", "centavo de barbados|centavos de barbados") + .put("Dólar beliceño", "dólar beliceño|dólares beliceños|bz$|bzd") + .put("Centavo beliceño", "centavo beliceño|centavos beliceños") + .put("Dólar bermudeño", "dólar bermudeño|dólares bermudeños|bd$|bmd") + .put("Centavo bermudeño", "centavo bermudeño|centavos bermudeños") + .put("Rublo bielorruso", "rublo bielorruso|rublos bielorrusos|byr") + .put("Kópek bielorruso", "kópek bielorruso|kópeks bielorrusos|kap") + .put("Kyat birmano", "kyat birmano|kyats birmanos|mmk") + .put("Pya birmano", "pya birmano|pyas birmanos") + .put("Boliviano", "boliviano|bolivianos|bob|bs") + .put("Centésimo Boliviano", "centésimo boliviano|centésimos bolivianos") + .put("Marco bosnioherzegovino", "marco convertible|marco bosnioherzegovino|marcos convertibles|marcos bosnioherzegovinos|bam") + .put("Feningas bosnioherzegovino", "feninga convertible|feninga bosnioherzegovina|feningas convertibles") + .put("Pula", "pula|bwp") + .put("Thebe", "thebe") + .put("Real brasileño", "real brasileño|reales brasileños|r$|brl") + .put("Centavo brasileño", "centavo brasileño|centavos brasileños") + .put("Dólar de Brunéi", "dólar de brunei|dólares de brunéi|bnd") + .put("Sen de Brunéi", "sen|sen de brunéi") + .put("Lev búlgaro", "lev búlgaro|leva búlgaros|lv|bgn") + .put("Stotinki búlgaro", "stotinka búlgaro|stotinki búlgaros") + .put("Franco de Burundi", "franco de burundi|francos de burundi|fbu|fib") + .put("Céntimo Burundi", "céntimo burundi|céntimos burundies") + .put("Ngultrum butanés", "ngultrum butanés|ngultrum butaneses|btn") + .put("Chetrum butanés", "chetrum butanés|chetrum butaneses") + .put("Escudo caboverdiano", "escudo caboverdiano|escudos caboverdianos|cve") + .put("Riel camboyano", "riel camboyano|rieles camboyanos|khr") + .put("Dólar canadiense", "dólar canadiense|dólares canadienses|c$|cad") + .put("Centavo canadiense", "centavo canadiense|centavos canadienses") + .put("Peso chileno", "peso chileno|pesos chilenos|cpl") + .put("Yuan chino", "yuan chino|yuanes chinos|yuan|yuanes|renminbi|rmb|cny|¥") + .put("Peso colombiano", "peso colombiano|pesos colombianos|cop|col$") + .put("Centavo colombiano", "centavo colombiano|centavos colombianos") + .put("Franco comorano", "franco comorano|francos comoranos|kmf|₣") + .put("Franco congoleño", "franco congoleño|francos congoleños|cdf") + .put("Céntimo congoleño", "céntimo congoleño|céntimos congoleños") + .put("Won norcoreano", "won norcoreano|wŏn norcoreano|wŏn norcoreanos|kpw") + .put("Chon norcoreano", "chon norcoreano|chŏn norcoreano|chŏn norcoreanos|chon norcoreanos") + .put("Won surcoreano", "wŏn surcoreano|won surcoreano|wŏnes surcoreanos|wones surcoreanos|krw") + .put("Chon surcoreano", "chon surcoreano|chŏn surcoreano|chŏn surcoreanos|chon surcoreanos") + .put("Colón costarricense", "colón costarricense|colones costarricenses|crc") + .put("Kuna croata", "kuna croata|kuna croatas|hrk") + .put("Lipa croata", "lipa croata|lipa croatas") + .put("Peso cubano", "peso cubano|pesos cubanos|cup") + .put("Peso cubano convertible", "peso cubano convertible|pesos cubanos convertible|cuc") + .put("Corona danesa", "corona danesa|coronas danesas|dkk") + .put("Libra egipcia", "libra egipcia|libras egipcias|egp|l.e.") + .put("Piastra egipcia", "piastra egipcia|piastras egipcias") + .put("Colón salvadoreño", "colón salvadoreño|colones salvadoreños|svc") + .put("Dirham de los Emiratos Árabes Unidos", "dirham|dirhams|dirham de los emiratos Árabes unidos|aed|dhs") + .put("Nakfa", "nakfa|nfk|ern") + .put("Céntimo de Nakfa", "céntimo de nakfa|céntimos de nakfa") + .put("Peseta", "peseta|pesetas|pts.|ptas.|esp") + .put("Dólar estadounidense", "dólar estadounidense|dólares estadounidenses|usd|u$d|us$") + .put("Corona estonia", "corona estonia|coronas estonias|eek") + .put("Senti estonia", "senti estonia|senti estonias") + .put("Birr etíope", "birr etíope|birr etíopes|br|etb") + .put("Santim etíope", "santim etíope|santim etíopes") + .put("Peso filipino", "peso filipino|pesos filipinos|php") + .put("Marco finlandés", "marco finlandés|marcos finlandeses") + .put("Dólar fiyiano", "dólar fiyiano|dólares fiyianos|fj$|fjd") + .put("Centavo fiyiano", "centavo fiyiano|centavos fiyianos") + .put("Dalasi", "dalasi|gmd") + .put("Bututs", "butut|bututs") + .put("Lari georgiano", "lari georgiano|lari georgianos|gel") + .put("Tetri georgiano", "tetri georgiano|tetri georgianos") + .put("Cedi", "cedi|ghs|gh₵") + .put("Pesewa", "pesewa") + .put("Libra gibraltareña", "libra gibraltareña|libras gibraltareñas|gip") + .put("Penique gibraltareña", "penique gibraltareña|peniques gibraltareñas") + .put("Quetzal guatemalteco", "quetzal guatemalteco|quetzales guatemaltecos|quetzal|quetzales|gtq") + .put("Centavo guatemalteco", "centavo guatemalteco|centavos guatemaltecos") + .put("Libra de Guernsey", "libra de guernsey|libras de guernsey|ggp") + .put("Penique de Guernsey", "penique de guernsey|peniques de guernsey") + .put("Franco guineano", "franco guineano|francos guineanos|gnf|fg") + .put("Céntimo guineano", "céntimo guineano|céntimos guineanos") + .put("Dólar guyanés", "dólar guyanés|dólares guyaneses|gyd|gy") + .put("Gourde haitiano", "gourde haitiano|gourde haitianos|htg") + .put("Céntimo haitiano", "céntimo haitiano|céntimos haitianos") + .put("Lempira hondureño", "lempira hondureño|lempira hondureños|hnl") + .put("Centavo hondureño", "centavo hondureño|centavos hondureño") + .put("Dólar de Hong Kong", "dólar de hong kong|dólares de hong kong|hk$|hkd") + .put("Forinto húngaro", "forinto húngaro|forinto húngaros|huf") + .put("Rupia india", "rupia india|rupias indias|inr") + .put("Paisa india", "paisa india|paise indias") + .put("Rupia indonesia", "rupia indonesia|rupias indonesias|idr") + .put("Sen indonesia", "sen indonesia|sen indonesias") + .put("Rial iraní", "rial iraní|rial iranies|irr") + .put("Dinar iraquí", "dinar iraquí|dinares iraquies|iqd") + .put("Fil iraquí", "fil iraquí|fils iraquies") + .put("Libra manesa", "libra manesa|libras manesas|imp") + .put("Penique manes", "penique manes|peniques maneses") + .put("Corona islandesa", "corona islandesa|coronas islandesas|isk|íkr") + .put("Aurar islandes", "aurar islandes|aurar islandeses") + .put("Dólar de las Islas Caimán", "dólar de las islas caimán|dólares de las islas caimán|ci$|kyd") + .put("Dólar de las Islas Cook", "dólar de las islas cook|dólares de las islas cook") + .put("Corona feroesa", "corona feroesa|coronas feroesas|fkr") + .put("Libra malvinense", "libra malvinense|libras malvinenses|fk£|fkp") + .put("Dólar de las Islas Salomón", "dólar de las islas salomón|dólares de las islas salomón|sbd") + .put("Nuevo shéquel", "nuevo shéquel|nuevos shéquel|ils") + .put("Agorot", "agorot") + .put("Dólar jamaiquino", "dólar jamaiquino|dólares jamaiquinos|j$|ja$|jmd") + .put("Yen", "yen|yenes|jpy") + .put("Libra de Jersey", "libra de jersey|libras de jersey|jep") + .put("Dinar jordano", "dinar jordano|dinares jordanos|jd|jod") + .put("Piastra jordano", "piastra jordano|piastras jordanos") + .put("Tenge kazajo", "tenge|tenge kazajo|kzt") + .put("Chelín keniano", "chelín keniano|chelines kenianos|ksh|kes") + .put("Som kirguís", "som kirguís|kgs") + .put("Tyiyn", "tyiyn") + .put("Dólar de Kiribati", "dólar de kiribati|dólares de kiribati") + .put("Dinar kuwaití", "dinar kuwaití|dinares kuwaití") + .put("Kip laosiano", "kip|kip laosiano|kip laosianos|lak") + .put("Att laosiano", "att|att laosiano|att laosianos") + .put("Loti", "loti|maloti|lsl") + .put("Sente", "sente|lisente") + .put("Libra libanesa", "libra libanesa|libras libanesas|lbp") + .put("Dólar liberiano", "dólar liberiano|dólares liberianos|l$|lrd") + .put("Dinar libio", "dinar libio|dinares libios|ld|lyd") + .put("Dirham libio", "dirham libio|dirhams libios") + .put("Litas lituana", "litas lituana|litai lituanas|ltl") + .put("Pataca macaense", "pataca macaense|patacas macaenses|mop$|mop") + .put("Avo macaense", "avo macaense|avos macaenses") + .put("Ho macaense", "ho macaense|ho macaenses") + .put("Denar macedonio", "denar macedonio|denare macedonios|den|mkd") + .put("Deni macedonio", "deni macedonio|deni macedonios") + .put("Ariary malgache", "ariary malgache|ariary malgaches|mga") + .put("Iraimbilanja malgache", "iraimbilanja malgache|iraimbilanja malgaches") + .put("Ringgit malayo", "ringgit malayo|ringgit malayos|rm|myr") + .put("Sen malayo", "sen malayo|sen malayos") + .put("Kwacha malauí", "kwacha malauí|mk|mwk") + .put("Támbala malauí", "támbala malauí") + .put("Rupia de Maldivas", "rupia de maldivas|rupias de maldivas|mvr") + .put("Dirham marroquí", "dirham marroquí|dirhams marroquies|mad") + .put("Rupia de Mauricio", "rupia de Mauricio|rupias de Mauricio|mur") + .put("Uguiya", "uguiya|uguiyas|mro") + .put("Jum", "jum|jums") + .put("Peso mexicano", "peso mexicano|pesos mexicanos|mxn") + .put("Centavo mexicano", "centavo mexicano|centavos mexicanos") + .put("Leu moldavo", "leu moldavo|lei moldavos|mdl") + .put("Ban moldavo", "ban moldavo|bani moldavos") + .put("Tugrik mongol", "tugrik mongol|tugrik|tugrik mongoles|tug|mnt") + .put("Metical mozambiqueño", "metical|metical mozambiqueño|meticales|meticales mozambiqueños|mtn|mzn") + .put("Dram de Nagorno Karabaj", "dram de nagorno karabaj|drams de nagorno karabaj") + .put("Luma de Nagorno Karabaj", "luma de nagorno karabaj") + .put("Dólar namibio", "dólar namibio|dólares namibios|n$|nad") + .put("Centavo namibio", "centavo namibio|centavos namibios") + .put("Rupia nepalí", "rupia nepalí|rupias nepalies|npr") + .put("Paisa nepalí", "paisa nepalí|paisas nepalies") + .put("Córdoba nicaragüense", "córdoba nicaragüense|córdobas nicaragüenses|nio") + .put("Centavo nicaragüense", "centavo nicaragüense|centavos nicaragüenses") + .put("Naira", "naira|ngn") + .put("Kobo", "kobo") + .put("Corona noruega", "corona noruega|coronas noruegas|nok") + .put("Franco CFP", "franco cfp|francos cfp|xpf") + .put("Dólar neozelandés", "dólar neozelandés|dólares neozelandeses|dólar de nueva zelanda|dólares de nueva zelanda|nz$|nzd") + .put("Centavo neozelandés", "centavo neozelandés|centavo de nueva zelanda|centavos de nueva zelanda|centavos neozelandeses") + .put("Rial omaní", "rial omaní|riales omanies|omr") + .put("Baisa omaní", "baisa omaní|baisa omanies") + .put("Florín neerlandés", "florín neerlandés|florines neerlandeses|nlg") + .put("Rupia pakistaní", "rupia pakistaní|rupias pakistanies|pkr") + .put("Paisa pakistaní", "paisa pakistaní|paisas pakistanies") + .put("Balboa panameño", "balboa panameño|balboa panameños|pab") + .put("Centésimo panameño", "centésimo panameño|centésimos panameños") + .put("Kina", "kina|pkg|pgk") + .put("Toea", "toea") + .put("Guaraní", "guaraní|guaranies|gs|pyg") + .put("Sol", "sol|soles|nuevo sol|pen|s#.") + .put("Céntimo de sol", "céntimo de sol|céntimos de sol") + .put("Złoty", "złoty|esloti|eslotis|zł|pln") + .put("Groszy", "groszy") + .put("Riyal qatarí", "riyal qatarí|riyal qataries|qr|qar") + .put("Dirham qatarí", "dirham qatarí|dirhams qataries") + .put("Libra esterlina", "libra esterlina|libras esterlinas|gbp") + .put("Corona checa", "corona checa|coronas checas|kc|czk") + .put("Peso dominicano", "peso dominicano|pesos dominicanos|rd$|dop") + .put("Centavo dominicano", "centavo dominicano|centavos dominicanos") + .put("Franco ruandés", "franco ruandés|francos ruandeses|rf|rwf") + .put("Céntimo ruandés", "céntimo ruandés|céntimos ruandeses") + .put("Leu rumano", "leu rumano|lei rumanos|ron") + .put("Ban rumano", "ban rumano|bani rumanos") + .put("Rublo ruso", "rublo ruso|rublos rusos|rub") + .put("Kopek ruso", "kopek ruso|kopeks rusos") + .put("Tala", "tala|tālā|ws$|sat|wst") + .put("Sene", "sene") + .put("Libra de Santa Helena", "libra de santa helena|libras de santa helena|shp") + .put("Penique de Santa Helena", "penique de santa helena|peniques de santa helena") + .put("Dobra", "dobra") + .put("Dinar serbio", "dinar serbio|dinares serbios|rsd") + .put("Para serbio", "para serbio|para serbios") + .put("Rupia de Seychelles", "rupia de seychelles|rupias de seychelles|scr") + .put("Centavo de Seychelles", "centavo de seychelles|centavos de seychelles") + .put("Leone", "leone|le|sll") + .put("Dólar de Singapur", "dólar de singapur|dólares de singapur|sgb") + .put("Centavo de Singapur", "centavo de Singapur|centavos de Singapur") + .put("Libra siria", "libra siria|libras sirias|s£|syp") + .put("Piastra siria", "piastra siria|piastras sirias") + .put("Chelín somalí", "chelín somalí|chelines somalies|sos") + .put("Centavo somalí", "centavo somalí|centavos somalies") + .put("Chelín somalilandés", "chelín somalilandés|chelines somalilandeses") + .put("Centavo somalilandés", "centavo somalilandés|centavos somalilandeses") + .put("Rupia de Sri Lanka", "rupia de Sri Lanka|rupias de Sri Lanka|lkr") + .put("Céntimo de Sri Lanka", "céntimo de Sri Lanka|céntimos de Sri Lanka") + .put("Lilangeni", "lilangeni|emalangeni|szl") + .put("Rand sudafricano", "rand|rand sudafricano|zar") + .put("Libra sudanesa", "libra sudanesa|libras sudanesas|sdg") + .put("Piastra sudanesa", "piastra sudanesa|piastras sudanesas") + .put("Libra sursudanesa", "libra sursudanesa|libras sursudanesa|ssp") + .put("Piastra sursudanesa", "piastra sursudanesa|piastras sursudanesas") + .put("Corona sueca", "corona sueca|coronas suecas|sek") + .put("Franco suizo", "franco suizo|francos suizos|sfr|chf") + .put("Rappen suizo", "rappen suizo|rappens suizos") + .put("Dólar surinamés", "óolar surinamés|dólares surinameses|srd") + .put("Centavo surinamés", "centavo surinamés|centavos surinamés") + .put("Baht tailandés", "baht tailandés|baht tailandeses|thb") + .put("Satang tailandés", "satang tailandés|satang tailandeses") + .put("Nuevo dólar taiwanés", "nuevo dólar taiwanés|dólar taiwanés|dólares taiwaneses|twd") + .put("Centavo taiwanés", "centavo taiwanés|centavos taiwaneses") + .put("Chelín tanzano", "chelín tanzano|chelines tanzanos|tzs") + .put("Centavo tanzano", "centavo tanzano|centavos tanzanos") + .put("Somoni tayiko", "somoni tayiko|somoni|tjs") + .put("Diram", "diram|dirams") + .put("Paʻanga", "dólar tongano|dólares tonganos|paʻanga|pa'anga|top") + .put("Seniti", "seniti") + .put("Rublo de Transnistria", "rublo de transnistria|rublos de transnistria") + .put("Kopek de Transnistria", "kopek de transnistria|kopeks de transnistria") + .put("Dólar trinitense", "dólar trinitense|dólares trinitenses|ttd") + .put("Centavo trinitense", "centavo trinitense|centavos trinitenses") + .put("Dinar tunecino", "dinar tunecino|dinares tunecinos|tnd") + .put("Millime tunecino", "millime tunecino|millimes tunecinos") + .put("Lira turca", "lira turca|liras turcas|try") + .put("Kuruş turca", "kuruş turca|kuruş turcas") + .put("Manat turkmeno", "manat turkmeno|manat turkmenos|tmt") + .put("Tennesi turkmeno", "tennesi turkmeno|tenge turkmeno") + .put("Dólar tuvaluano", "dólar tuvaluano|dólares tuvaluanos") + .put("Centavo tuvaluano", "centavo tuvaluano|centavos tuvaluanos") + .put("Grivna", "grivna|grivnas|uah") + .put("Kopiyka", "kopiyka|kópeks") + .put("Chelín ugandés", "chelín ugandés|chelines ugandeses|ugx") + .put("Centavo ugandés", "centavo ugandés|centavos ugandeses") + .put("Peso uruguayo", "peso uruguayo|pesos uruguayos|uyu") + .put("Centésimo uruguayo", "centésimo uruguayo|centésimos uruguayos") + .put("Som uzbeko", "som uzbeko|som uzbekos|uzs") + .put("Tiyin uzbeko", "tiyin uzbeko|tiyin uzbekos") + .put("Vatu", "vatu|vuv") + .put("Bolívar fuerte", "bolívar fuerte|bolívar|bolívares|vef") + .put("Céntimo de bolívar", "céntimo de bolívar|céntimos de bolívar") + .put("Đồng vietnamita", "Đồng vietnamita|dong vietnamita|dong vietnamitas|vnd") + .put("Hào vietnamita", "Hào vietnamita|hao vietnamita|hao vietnamitas") + .put("Rial yemení", "rial yemení|riales yemenies|yer") + .put("Fils yemení", "fils yemení|fils yemenies") + .put("Franco yibutiano", "franco yibutiano|francos yibutianos|djf") + .put("Dinar yugoslavo", "dinar yugoslavo|dinares yugoslavos|yud") + .put("Kwacha zambiano", "kwacha zambiano|kwacha zambianos|zmw") + .put("Ngwee zambiano", "ngwee zambiano|ngwee zambianos") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dobra", "db|std") + .put("Dólar", "$") + .put("Dólar estadounidense", "us$|u$d|usd") + .put("Dólar del Caribe Oriental", "ec$|xcd") + .put("Dólar australiano", "a$|aud") + .put("Dólar bahameño", "b$|bsd") + .put("Dólar de Barbados", "bds$|bbd") + .put("Dólar beliceño", "bz$|bzd") + .put("Dólar bermudeño", "bd$|bmd") + .put("Dólar de Brunéi", "brunéi $|bnd") + .put("Dólar de Singapur", "s$|sgd") + .put("Dólar canadiense", "c$|can$|cad") + .put("Dólar de las Islas Caimán", "ci$|kyd") + .put("Dólar neozelandés", "nz$|nzd") + .put("Dólar fiyiano", "fj$|fjd") + .put("Dólar guyanés", "gy$|gyd") + .put("Dólar de Hong Kong", "hk$|hkd") + .put("Dólar jamaiquino", "j$|ja$|jmd") + .put("Dólar liberiano", "l$|lrd") + .put("Dólar namibio", "n$|nad") + .put("Dólar de las Islas Salomón", "si$|sbd") + .put("Nuevo dólar taiwanés", "nt$|twd") + .put("Real brasileño", "r$|brl") + .put("Guaraní", "₲|gs.|pyg") + .put("Dólar trinitense", "tt$|ttd") + .put("Yuan chino", "¥|cny|rmb") + .put("Yen", "¥|jpy") + .put("Euro", "€|eur") + .put("Florín", "ƒ") + .put("Libra", "£") + .put("Colón costarricense", "₡") + .put("Lira turca", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("le", "db", "std"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("bit", "bit|bits") + .put("kilobit", "kilobit|kilobits|kb|kbit") + .put("megabit", "megabit|megabits|Mb|Mbit") + .put("gigabit", "gigabit|gigabits|Gb|Gbit") + .put("terabit", "terabit|terabits|Tb|Tbit") + .put("petabit", "petabit|petabits|Pb|Pbit") + .put("kibibit", "kibibit|kibibits|kib|kibit") + .put("mebibit", "mebibit|mebibits|Mib|Mibit") + .put("gibibit", "gibibit|gibibits|Gib|Gibit") + .put("tebibit", "tebibit|tebibits|Tib|Tibit") + .put("pebibit", "pebibit|pebibits|Pib|Pibit") + .put("byte", "byte|bytes") + .put("kilobyte", "kilobyte|kilobytes|kB|kByte") + .put("megabyte", "megabyte|megabytes|MB|MByte") + .put("gigabyte", "gigabyte|gigabytes|GB|GByte") + .put("terabyte", "terabyte|terabytes|TB|TByte") + .put("petabyte", "petabyte|petabytes|PB|PByte") + .put("kibibyte", "kibibyte|kibibytes|kiB|kiByte") + .put("mebibyte", "mebibyte|mebibytes|MiB|MiByte") + .put("gibibyte", "gibibyte|gibibytes|GiB|GiByte") + .put("tebibyte", "tebibyte|tebibytes|TiB|TiByte") + .put("pebibyte", "pebibyte|pebibytes|PiB|PiByte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("al", "mi", "área", "áreas", "pie", "pies", "\""); + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilómetro", "km|kilometro|kilómetro|kilometros|kilómetros") + .put("Hectómetro", "hm|hectometro|hectómetro|hectometros|hectómetros") + .put("Decámetro", "decametro|decámetro|decametros|decámetros|dam") + .put("Metro", "m|m.|metro|metros") + .put("Decímetro", "dm|decimetro|decímetro|decimetros|decímetros") + .put("Centímetro", "cm|centimetro|centímetro|centimetros|centimetros") + .put("Milímetro", "mm|milimetro|milímetro|milimetros|milímetros") + .put("Micrómetro", "µm|um|micrometro|micrómetro|micrometros|micrómetros|micrón|micrónes") + .put("Nanómetro", "nm|nanometro|nanómetro|nanometros|nanómetros") + .put("Picómetro", "pm|picometro|picómetro|picometros|picómetros") + .put("Milla", "mi|milla|millas") + .put("Yarda", "yd|yarda|yardas") + .put("Pulgada", "pulgada|pulgadas|\"") + .put("Pie", "pie|pies|ft") + .put("Año luz", "año luz|años luz|al") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("mi", "área", "áreas", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^|\\P{L}))"; + + public static final String BuildSuffix = "(?=(\\s|\\P{L}|$))"; + + public static final String ConnectorToken = "de"; + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Metro por segundo", "metro/segundo|m/s|metro por segundo|metros por segundo|metros por segundos") + .put("Kilómetro por hora", "km/h|kilómetro por hora|kilometro por hora|kilómetros por hora|kilometros por hora|kilómetro/hora|kilometro/hora|kilómetros/hora|kilometros/hora") + .put("Kilómetro por minuto", "km/min|kilómetro por minuto|kilometro por minuto|kilómetros por minuto|kilometros por minuto|kilómetro/minuto|kilometro/minuto|kilómetros/minuto|kilometros/minuto") + .put("Kilómetro por segundo", "km/seg|kilómetro por segundo|kilometro por segundo|kilómetros por segundo|kilometros por segundo|kilómetro/segundo|kilometro/segundo|kilómetros/segundo|kilometros/segundo") + .put("Milla por hora", "mph|milla por hora|mi/h|milla/hora|millas/hora|millas por hora") + .put("Nudo", "kt|nudo|nudos|kn") + .put("Pie por segundo", "ft/s|pie/s|ft/seg|pie/seg|pie por segundo|pies por segundo") + .put("Pie por minuto", "ft/min|pie/min|pie por minuto|pies por minuto") + .put("Yarda por minuto", "yardas por minuto|yardas/minuto|yardas/min") + .put("Yarda por segundo", "yardas por segundo|yardas/segundo|yardas/seg") + .build(); + + public static final List AmbiguousSpeedUnitList = Arrays.asList("nudo", "nudos"); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("Kelvin", "k|K|kelvin") + .put("Rankine", "r|rankine") + .put("Grado Celsius", "°c|grados c|grado celsius|grados celsius|celsius|grado centígrado|grados centígrados|centígrado|centígrados") + .put("Grado Fahrenheit", "°f|grados f|grado fahrenheit|grados fahrenheit|fahrenheit") + .put("Grado Réaumur", "°r|°re|grados r|grado réaumur|grados réaumur|réaumur") + .put("Grado Delisle", "°d|grados d|grado delisle|grados delisle|delisle") + .put("Grado", "°|grados|grado") + .build(); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Kilómetro cúbico", "kilómetro cúbico|kilómetros cúbico|km3|km^3|km³") + .put("Hectómetro cúbico", "hectómetro cúbico|hectómetros cúbico|hm3|hm^3|hm³") + .put("Decámetro cúbico", "decámetro cúbico|decámetros cúbico|dam3|dam^3|dam³") + .put("Metro cúbico", "metro cúbico|metros cúbico|m3|m^3|m³") + .put("Decímetro cúbico", "decímetro cúbico|decímetros cúbico|dm3|dm^3|dm³") + .put("Centímetro cúbico", "centímetro cúbico|centímetros cúbico|cc|cm3|cm^3|cm³") + .put("Milímetro cúbico", "milímetro cúbico|milímetros cúbico|mm3|mm^3|mm³") + .put("Pulgada cúbica", "pulgada cúbica|pulgadas cúbicas") + .put("Pie cúbico", "pie cúbico|pies cúbicos|pie3|pie^3|pie³|ft3|ft^3|ft³") + .put("Yarda cúbica", "yarda cúbica|yardas cúbicas|yd3|yd^3|yd³") + .put("Hectolitro", "hectolitro|hectolitros|hl") + .put("Litro", "litro|litros|lts|l") + .put("Mililitro", "mililitro|mililitros|ml") + .put("Galón", "galón|galones") + .put("Pinta", "pinta|pintas") + .put("Barril", "barril|barriles|bbl") + .put("Onza líquida", "onza líquida|onzas líquidas") + .build(); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Tonelada métrica", "tonelada métrica|toneladas métricas") + .put("Tonelada", "ton|tonelada|toneladas") + .put("Kilogramo", "kg|kilogramo|kilogramos") + .put("Hectogramo", "hg|hectogramo|hectogramos") + .put("Decagramo", "dag|decagramo|decagramos") + .put("Gramo", "g|gr|gramo|gramos") + .put("Decigramo", "dg|decigramo|decigramos") + .put("Centigramo", "cg|centigramo|centigramos") + .put("Miligramo", "mg|miligramo|miligramos") + .put("Microgramo", "µg|ug|microgramo|microgramos") + .put("Nanogramo", "ng|nanogramo|nanogramos") + .put("Picogramo", "pg|picogramo|picogramos") + .put("Libra", "lb|libra|libras") + .put("Onza", "oz|onza|onzas") + .put("Grano", "grano|granos") + .put("Quilate", "ct|quilate|quilates") + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..3255c1639 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = SpanishNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..161395874 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = SpanishNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..2de60c673 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = SpanishNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = SpanishNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..7df9fe163 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(SpanishNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..734dd420d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = SpanishNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..8043d7368 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,76 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.spanish.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class SpanishNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected SpanishNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit);; + this.compoundUnitConnectorRegex = + Pattern.compile(SpanishNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(SpanishNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return SpanishNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return SpanishNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return SpanishNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..3610df8eb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = SpanishNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..9f8016357 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map TemperatureSuffixList = new HashMap(SpanishNumericWithUnit.TemperatureSuffixList); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..5b0b85325 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map VolumeSuffixList = SpanishNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..89dfa2e62 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map WeightSuffixList = SpanishNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..12b25d8dd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..6ca384127 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..0bad7646d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..dc95e0bee --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..28b6dfb5e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..9eb6f824b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.number.spanish.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +public class SpanishNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return SpanishNumericWithUnit.ConnectorToken; + } + + public SpanishNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(NumberMode.Default); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new SpanishNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..93f905f96 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..86a075361 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..c47e5ae22 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..ecc4e5261 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java new file mode 100644 index 000000000..38c88abbf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.numberwithunit.utilities; + +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class DictionaryUtils { + + /** + * Safely bind dictionary which contains several key-value pairs to the destination dictionary. + * This function is used to bind all the prefix and suffix for units. + */ + public static void bindDictionary(Map dictionary, + Map sourceDictionary) { + if (dictionary == null) { + return; + } + + for (Map.Entry pair : dictionary.entrySet()) { + if (pair.getKey() == null || pair.getKey().isEmpty()) { + continue; + } + + bindUnitsString(sourceDictionary, pair.getKey(), pair.getValue()); + } + } + + /** + * Bind keys in a string which contains words separated by '|'. + */ + public static void bindUnitsString(Map sourceDictionary, String key, String source) { + String[] values = source.trim().split(Pattern.quote("|")); + + for (String token : values) { + if (token.isEmpty() || sourceDictionary.containsKey(token)) { + continue; + } + + sourceDictionary.put(token, key); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java new file mode 100644 index 000000000..46fbc0bfd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.numberwithunit.utilities; + +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.util.Comparator; + +public class StringComparer implements Comparator { + @Override + public int compare(String stringA, String stringB) { + if (StringUtility.isNullOrEmpty(stringA) && StringUtility.isNullOrEmpty(stringB)) { + return 0; + } else { + if (StringUtility.isNullOrEmpty(stringB)) { + return -1; + } + if (StringUtility.isNullOrEmpty(stringA)) { + return 1; + } + int stringComparedLength = stringB.length() - stringA.length(); + + if (stringComparedLength != 0) { + return stringComparedLength; + } else { + return stringA.compareToIgnoreCase(stringB); + } + } + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java new file mode 100644 index 000000000..049b1b39c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java @@ -0,0 +1,132 @@ +package com.microsoft.recognizers.text.resources; + +import com.microsoft.recognizers.text.resources.datatypes.*; +import com.microsoft.recognizers.text.resources.writters.*; +import org.yaml.snakeyaml.TypeDescription; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Map; + +public class CodeGenerator { + + private static final String lineBreak = "\n"; + + private static final String headerComment = String.join( + lineBreak, + "// ------------------------------------------------------------------------------", + "// ", + "// This code was generated by a tool.", + "// Changes to this file may cause incorrect behavior and will be lost if", + "// the code is regenerated.", + "// ", + "//", + "// Copyright (c) Microsoft Corporation. All rights reserved.", + "// Licensed under the MIT License.", + "// ------------------------------------------------------------------------------"); + + public static void Generate(Path yamlFilePath, Path outputFilePath, String header, String footer) throws IOException { + + // Config YAML parser + Constructor constructor = new Constructor(); + constructor.addTypeDescription(new TypeDescription(ParamsRegex.class, "!paramsRegex")); + constructor.addTypeDescription(new TypeDescription(SimpleRegex.class, "!simpleRegex")); + constructor.addTypeDescription(new TypeDescription(NestedRegex.class, "!nestedRegex")); + constructor.addTypeDescription(new TypeDescription(Character.class, "!char")); + constructor.addTypeDescription(new TypeDescription(Boolean.class, "!bool")); + constructor.addTypeDescription(new TypeDescription(Dictionary.class, "!dictionary")); + constructor.addTypeDescription(new TypeDescription(List.class, "!list")); + + // Read and Parse YAML + Yaml yaml = new Yaml(constructor); + Map raw = yaml.load(new InputStreamReader(new FileInputStream(yamlFilePath.toString()), StandardCharsets.UTF_8)); + + // Transform + String[] lines = GenerateCodeLines(raw); + + // Write to file + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFilePath.toString()), StandardCharsets.UTF_8)); + writer.write(headerComment); + writer.write(lineBreak); + writer.write(lineBreak); + writer.write(header); + writer.write(lineBreak); + + BufferedWriter finalWriter = writer; + for (String l : lines) { + writer.write(lineBreak); + finalWriter.write(l); + writer.write(lineBreak); + } + + writer.write(footer); + writer.write(lineBreak); + } catch (Exception e) { + throw e; + } finally { + try { + writer.close(); + } catch (Exception e) { + throw e; + } + } + } + + private static String[] GenerateCodeLines(Map raw) { + return raw.entrySet().stream().map(kv -> { + String tokenName = kv.getKey(); + Object token = kv.getValue(); + return getWriter(tokenName, token).write(); + }).toArray(size -> new String[size]); + } + + private static ICodeWriter getWriter(String tokenName, Object token) throws IllegalArgumentException { + if (token instanceof ParamsRegex) { + return new ParamsRegexWriter(tokenName, (ParamsRegex)token); + } + + if (token instanceof SimpleRegex) { + return new SimpleRegexWriter(tokenName, (SimpleRegex)token); + } + + if (token instanceof NestedRegex) { + return new NestedRegexWriter(tokenName, (NestedRegex)token); + } + + if (token instanceof Character) { + return new CharacterWriter(tokenName, (char)token); + } + + if (token instanceof Boolean) { + return new BooleanWriter(tokenName, (boolean)token); + } + + if (token instanceof String) { + return new DefaultWriter(tokenName, (String)token); + } + + if (token instanceof Integer) { + return new IntegerWriter(tokenName, (int)token); + } + + if (token instanceof ArrayList) { + return new ListWriter(tokenName, "String", (String[])((ArrayList) token).stream().map(o -> o.toString()).toArray(size -> new String[size])); + } + + if (token instanceof List) { + return new ListWriter(tokenName, ((List)token).types[0], ((List)token).entries); + } + + if (token instanceof Dictionary) { + return new DictionaryWriter(tokenName, (Dictionary)token); + } + + throw new IllegalArgumentException(String.format("Data Type not supported for %s: %s", tokenName, token)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java new file mode 100644 index 000000000..b4b72a1af --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.resources; + +public class ResourceConfig { + public String[] input; + public String output; + public String[] header; + public String[] footer; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java new file mode 100644 index 000000000..6beee11a9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.resources; + +import java.util.List; + +public class ResourceDefinitions { + public String outputPath; + public List configFiles; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java new file mode 100644 index 000000000..f0b58c697 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java @@ -0,0 +1,49 @@ +package com.microsoft.recognizers.text.resources; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.*; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.stream.Collectors; + +public class ResourcesGenerator { + + private static final String ResourcesPath = "../Patterns"; + + private static final String LineBreak = "\n"; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + throw new Exception("Please specify path to pattern/resource file."); + } + + for (String resourceDefinitionFilePath : args) { + + ResourceDefinitions definition = Parse(resourceDefinitionFilePath); + definition.configFiles.forEach(config -> { + Path inputPath = FileSystems.getDefault().getPath(ResourcesPath, String.join(File.separator, config.input) + ".yaml"); + Path outputPath = FileSystems.getDefault().getPath(definition.outputPath, config.output + ".java"); + System.out.println(String.format("%s => %s", inputPath.toString(), outputPath.toString())); + + String header = String.join(LineBreak, config.header); + String footer = String.join(LineBreak, config.footer); + + try { + CodeGenerator.Generate(inputPath, outputPath, header, footer); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + } + + private static ResourceDefinitions Parse(String resourceDefinitionFile) throws IOException { + Reader reader = new InputStreamReader(new FileInputStream(resourceDefinitionFile), "utf-8"); + BufferedReader br = new BufferedReader(reader); + + String json = br.lines().collect(Collectors.joining()); + + return new ObjectMapper().readValue(json, ResourceDefinitions.class); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java new file mode 100644 index 000000000..971237ae8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +import java.util.Map; + +public class Dictionary { + public String[] types; + public Map entries; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java new file mode 100644 index 000000000..e0b52a1d8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class List { + public String[] types; + public String[] entries; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java new file mode 100644 index 000000000..267fc731f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class NestedRegex { + public String def; + public String[] references; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java new file mode 100644 index 000000000..9e33e395f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class ParamsRegex { + public String def; + public String[] params; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java new file mode 100644 index 000000000..6f1d2dd97 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java @@ -0,0 +1,5 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class SimpleRegex { + public String def; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java new file mode 100644 index 000000000..d3481bfa9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java @@ -0,0 +1,20 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class BooleanWriter implements ICodeWriter { + + private final String name; + private final boolean value; + + public BooleanWriter(String name, boolean value) { + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final Boolean %s = %s;", + this.name, + sanitize(String.valueOf(this.value))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java new file mode 100644 index 000000000..d874fd0ef --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java @@ -0,0 +1,20 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class CharacterWriter implements ICodeWriter { + + private final String name; + private final char value; + + public CharacterWriter(String name, char value) { + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final Character %s = \'%s\';", + this.name, + sanitize(String.valueOf(this.value))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java new file mode 100644 index 000000000..ed6bffa71 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class DefaultWriter implements ICodeWriter { + + private final String name; + private final String value; + + public DefaultWriter(String name, String value) { + + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final String %s = \"%s\";", + this.name, + sanitize(this.value)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java new file mode 100644 index 000000000..1b8aa475e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java @@ -0,0 +1,94 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.Dictionary; +import com.microsoft.recognizers.text.resources.datatypes.List; + +import java.util.ArrayList; + +public class DictionaryWriter implements ICodeWriter { + + private final String name; + private final Dictionary def; + + public DictionaryWriter(String name, Dictionary def) { + this.name = name; + this.def = def; + } + + @Override + public String write() { + + String keyType = toJavaType(this.def.types[0]); + String valueType = toJavaType(this.def.types[1]); + + String keyQuote; + if(keyType.equals("Long") || keyType.equals("Double")) { + keyQuote = ""; + } else if(keyType.equals("Character")) { + keyQuote = "'"; + } else { + keyQuote = "\""; + } + + String valueQuote1; + String valueQuote2; + String prefix; + boolean hasList = false; + if (valueType.endsWith("[]")) { + hasList = true; + valueQuote1 = "{"; + valueQuote2 = "}"; + } else if(valueType.equals("Integer") || valueType.equals("Long") || valueType.equals("Double")) { + valueQuote1 = valueQuote2 = ""; + } else if(valueType.equals("Character")) { + valueQuote1 = valueQuote2 = "'"; + } else { + valueQuote1 = valueQuote2 = "\""; + } + + if (hasList) { + prefix = String.format("new %s", valueType); + } else { + prefix = ""; + } + + String[] entries = this.def.entries.entrySet().stream() + .map(kv -> String.format("\n .put(%s%s%s, %s%s%s%s)", keyQuote, sanitize(kv.getKey(), keyType), keyQuote, prefix, valueQuote1, getEntryValue(kv.getValue(), valueType), valueQuote2)) + .toArray(size -> new String[size]); + + return String.format( + " public static final ImmutableMap<%s, %s> %s = ImmutableMap.<%s, %s>builder()%s\n .build();", + keyType, + valueType, + this.name, + keyType, + valueType, + String.join("", entries)); + } + + private String getEntryValue(Object value, String valueType) { + if (value instanceof ArrayList) { + return String.join(", ", (String[])((ArrayList) value).stream().map(o -> String.format("\"%s\"", sanitize(o.toString(), valueType))).toArray(size -> new String[size])); + } + return sanitize(value.toString(), valueType); + } + + private String toJavaType(String type) { + switch (type) { + case "string": + return "String"; + case "char": + return "Character"; + case "long": + return "Long"; + case "int": + return "Integer"; + case "double": + return "Double"; + case "string[]": + return "String[]"; + default: + throw new IllegalArgumentException("Type '" + type + "' is not supported."); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java new file mode 100644 index 000000000..1bc908698 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java @@ -0,0 +1,36 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public interface ICodeWriter { + String write(); + + default String sanitize(String input) { + ObjectMapper mapper = new ObjectMapper(); + try { + String stringified = mapper.writeValueAsString(input); + return stringified.substring(1, stringified.length() - 1); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } + + Map NumericTypes = ImmutableMap.of("Double", "D", "Long", "L"); + + default String sanitize(String input, String valueType) { + if(valueType.equals("Character")) { + return input.replace("'", "\\'"); + } + + if(NumericTypes.containsKey(valueType)) { + return input + (NumericTypes.get(valueType)); + } + + return sanitize(input); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java new file mode 100644 index 000000000..17e5f65c7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class IntegerWriter implements ICodeWriter { + + private final String name; + private final int value; + + public IntegerWriter(String name, int value) { + + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final int %s = %s;", + this.name, + this.value); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java new file mode 100644 index 000000000..67a61c29c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.resources.writters; + +import java.util.Arrays; + +public class ListWriter implements ICodeWriter { + + private final String name; + private final String type; + private final String[] items; + + public ListWriter(String name, String type, String[] items) { + this.name = name; + this.type = type; + this.items = items; + } + + @Override + public String write() { + String typeName = this.type.equalsIgnoreCase("char") ? "Character" : "String"; + String quoteChar = this.type.equalsIgnoreCase("char") ? "'" : "\""; + String[] stringItems = Arrays.stream(this.items) + .map(s -> quoteChar + sanitize(s) + quoteChar) + .toArray(size -> new String[size]); + + return String.format( + " public static final List<%s> %s = Arrays.asList(%s);", + typeName, + this.name, + String.join(", ", stringItems)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java new file mode 100644 index 000000000..37f172c27 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java @@ -0,0 +1,24 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.NestedRegex; + +import java.util.Arrays; + +public class NestedRegexWriter implements ICodeWriter { + + private final String name; + private final NestedRegex def; + + public NestedRegexWriter(String name, NestedRegex def) { + this.name = name; + this.def = def; + } + + @Override + public String write() { + String replace = String.join("", Arrays.stream(this.def.references).map(p -> "\n .replace(\"{" + p + "}\", " + p + ")").toArray(size -> new String[size])); + + String template = " public static final String %s = \"%s\"%s;"; + return String.format(template, this.name, sanitize(this.def.def), replace); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java new file mode 100644 index 000000000..be1d839fa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.ParamsRegex; + +import java.util.Arrays; + +public class ParamsRegexWriter implements ICodeWriter { + + private final String name; + private ParamsRegex params; + + public ParamsRegexWriter(String name, ParamsRegex params) { + this.name = name; + this.params = params; + } + + @Override + public String write() { + String parameters = String.join(", ", Arrays.stream(this.params.params).map(p -> "String " + p).toArray(size -> new String[size])); + String replace = String.join("", Arrays.stream(this.params.params).map(p -> "\n .replace(\"{" + p + "}\", " + p + ")").toArray(size -> new String[size])); + + String template = String.join( + "\n ", + " public static String %s(%s) {", + " return \"%s\"%s;", + "}"); + return String.format(template, this.name, parameters, sanitize(this.params.def), replace); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java new file mode 100644 index 000000000..95817bb7e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.SimpleRegex; + +public class SimpleRegexWriter implements ICodeWriter { + + private String name; + private SimpleRegex def; + + public SimpleRegexWriter(String name, SimpleRegex def) { + this.name = name; + this.def = def; + } + + @Override + public String write() { + return String.format(" public static final String %s = \"%s\";", this.name, sanitize(this.def.def)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java new file mode 100644 index 000000000..14cc5ba96 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.utilities; + +public class Capture { + public final String value; + public final int index; + public final int length; + + public Capture(String value, int index, int length) { + this.value = value; + this.index = index; + this.length = length; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java new file mode 100644 index 000000000..4207c9480 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java @@ -0,0 +1,25 @@ +package com.microsoft.recognizers.text.utilities; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class DefinitionLoader { + + public static Map loadAmbiguityFilters(Map filters) { + + HashMap ambiguityFiltersDict = new HashMap<>(); + + for (Map.Entry pair : filters.entrySet()) { + + if (!"null".equals(pair.getKey())) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + return ambiguityFiltersDict; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java new file mode 100644 index 000000000..eee99eedf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.utilities; + +public abstract class DoubleUtility { + public static boolean canParse(String value) { + try { + Double.parseDouble(value); + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java new file mode 100644 index 000000000..a1b34f28f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java @@ -0,0 +1,83 @@ +package com.microsoft.recognizers.text.utilities; + +import java.text.Normalizer; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class FormatUtility { + public static String preprocess(String query) { + return FormatUtility.preprocess(query, true); + } + + public static String preprocess(String query, boolean toLower) { + if (toLower) { + query = query.toLowerCase(); + } + + return query + .replace('0', '0') + .replace('1', '1') + .replace('2', '2') + .replace('3', '3') + .replace('4', '4') + .replace('5', '5') + .replace('6', '6') + .replace('7', '7') + .replace('8', '8') + .replace('9', '9') + .replace(':', ':') + .replace('-', '-') + .replace(',', ',') + .replace('/', '/') + .replace('G', 'G') + .replace('M', 'M') + .replace('T', 'T') + .replace('K', 'K') + .replace('k', 'k') + .replace('.', '.') + .replace('(', '(') + .replace(')', ')'); + } + + public static String trimEnd(String input) { + return input.replaceAll("\\s+$", ""); + } + + public static String trimEnd(String input, CharSequence chars) { + return input.replaceAll("[" + Pattern.quote(chars.toString()) + "]+$", ""); + } + + public static List split(String input, List delimiters) { + String delimitersRegex = String.join( + "|", + delimiters.stream() + .map(s -> Pattern.quote(s)) + .collect(Collectors.toList())); + + return Arrays.stream(input.split(delimitersRegex)).filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + public static String removeDiacritics(String query) { + if (query == null) { + return null; + } + + String norm = Normalizer.normalize(query, Normalizer.Form.NFD); + int j = 0; + char[] out = new char[query.length()]; + for (int i = 0, n = norm.length(); i < n; ++i) { + char c = norm.charAt(i); + int type = Character.getType(c); + + if (type != Character.NON_SPACING_MARK) { + out[j] = c; + j++; + } + } + + return new String(out); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java new file mode 100644 index 000000000..9d7e11cd0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.utilities; + +public abstract class IntegerUtility { + public static boolean canParse(String value) { + try { + Integer.parseInt(value); + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java new file mode 100644 index 000000000..bbed9c58e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java @@ -0,0 +1,25 @@ +package com.microsoft.recognizers.text.utilities; + +import java.util.Map; + +public class Match { + public final int index; + public final int length; + public final String value; + public final Map innerGroups; + + public Match(int index, int length, String value, Map innerGroups) { + this.index = index; + this.length = length; + this.value = value; + this.innerGroups = innerGroups; + } + + public MatchGroup getGroup(String key) { + if (innerGroups.containsKey(key)) { + return innerGroups.get(key); + } + + return new MatchGroup("", 0, 0, new Capture[0]); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java new file mode 100644 index 000000000..15d8e8cc0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.utilities; + +public class MatchGroup { + public final String value; + public final int index; + public final int length; + public final Capture[] captures; + + public MatchGroup(String value, int index, int length, Capture[] captures) { + this.value = value; + this.index = index; + this.length = length; + this.captures = captures; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java new file mode 100644 index 000000000..c857d64fd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java @@ -0,0 +1,120 @@ +package com.microsoft.recognizers.text.utilities; + +import java.text.Normalizer; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class QueryProcessor { + + + public static String preprocess(String query) { + return QueryProcessor.preprocess(query, false, true); + } + + public static String preprocess(String query, boolean caseSensitive) { + return QueryProcessor.preprocess(query, caseSensitive, true); + } + + public static String preprocess(String query, boolean caseSensitive, boolean recode) { + + if (recode) { + query = query.replace('0', '0') + .replace('1', '1') + .replace('2', '2') + .replace('3', '3') + .replace('4', '4') + .replace('5', '5') + .replace('6', '6') + .replace('7', '7') + .replace('8', '8') + .replace('9', '9') + .replace(':', ':') + .replace('-', '-') + .replace(',', ',') + .replace('/', '/') + .replace('G', 'G') + .replace('M', 'M') + .replace('T', 'T') + .replace('K', 'K') + .replace('k', 'k') + .replace('.', '.') + .replace('(', '(') + .replace(')', ')') + .replace('%', '%') + .replace('、', ','); + } + + if (!caseSensitive) { + query = query.toLowerCase(); + } else { + query = toLowerTermSensitive(query); + } + + return query; + } + + private static final String tokens = "(kB|K[Bb]?|M[BbM]?|G[Bb]?|B)"; + private static final String expression = "(?<=(\\s|\\d))" + tokens + "\\b"; + private static final Pattern special_tokens_regex = Pattern.compile(expression, Pattern.UNICODE_CHARACTER_CLASS); + + private static String toLowerTermSensitive(String input) { + + char[] inputChars = input.toLowerCase(Locale.ROOT).toCharArray(); + + Match[] matches = RegExpUtility.getMatches(special_tokens_regex, input); + for (Match match : matches) { + QueryProcessor.applyReverse(match.index, inputChars, match.value); + } + + return new String(inputChars); + } + + private static void applyReverse(int index, char[] inputChars, String value) { + for (int i = 0; i < value.length(); ++i) { + inputChars[index + i] = value.charAt(i); + } + } + + public static String trimEnd(String input) { + return input.replaceAll("\\s+$", ""); + } + + public static String trimEnd(String input, CharSequence chars) { + return input.replaceAll("[" + Pattern.quote(chars.toString()) + "]+$", ""); + } + + public static List split(String input, List delimiters) { + String delimitersRegex = String.join( + "|", + delimiters.stream() + .map(s -> Pattern.quote(s)) + .collect(Collectors.toList())); + + return Arrays.stream(input.split(delimitersRegex)).filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + public static String removeDiacritics(String query) { + if (query == null) { + return null; + } + + String norm = Normalizer.normalize(query, Normalizer.Form.NFD); + int j = 0; + char[] out = new char[query.length()]; + for (int i = 0, n = norm.length(); i < n; ++i) { + char c = norm.charAt(i); + int type = Character.getType(c); + + if (type != Character.NON_SPACING_MARK) { + out[j] = c; + j++; + } + } + + return new String(out); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java new file mode 100644 index 000000000..41612c136 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java @@ -0,0 +1,419 @@ +package com.microsoft.recognizers.text.utilities; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public abstract class RegExpUtility { + + private static final Pattern matchGroup = Pattern.compile("\\?<(?\\w+)>", Pattern.CASE_INSENSITIVE); + private static final Pattern matchGroupNames = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>", Pattern.CASE_INSENSITIVE); + private static final Pattern matchPositiveLookbehind = Pattern.compile("\\(\\?<=", Pattern.CASE_INSENSITIVE); + private static final Pattern matchNegativeLookbehind = Pattern.compile("\\(\\? bindings = new HashMap() { + { + put('+', "{1,10}"); + put('*', "{0,10}"); + } + }; + + public static Pattern getSafeRegExp(String source) { + return getSafeRegExp(source, 0); + } + + public static Pattern getSafeRegExp(String source, int flags) { + String sanitizedSource = sanitizeGroups(source); + return Pattern.compile(sanitizedSource, flags); + } + + public static Map getNamedGroups(Matcher groupedMatcher) { + return getNamedGroups(groupedMatcher, false); + } + + public static Map getNamedGroups(Matcher groupedMatcher, boolean sanitize) { + + Map matchedGroups = new LinkedHashMap<>(); + Matcher m = matchGroupNames.matcher(groupedMatcher.pattern().pattern()); + + while (m.find()) { + String groupName = m.group(1); + String groupValue = groupedMatcher.group(groupName); + if (sanitize && groupName.contains(groupNameIndexSep)) { + groupName = groupName.substring(0, groupName.lastIndexOf(groupNameIndexSep)); + } + + if (!groupName.contains(groupNameIndexSep)) { + groupName = groupName.replace("ii", "_"); + } + + // If matchedGroups previously contained a mapping for groupName, the old value is replaced. + if (groupValue != null) { + matchedGroups.put(groupName, groupValue); + } + } + + return matchedGroups; + } + + public static Match[] getMatches(Pattern regex, String source) { + + if (regex == null) { + return new Match[0]; + } + + String rawRegex = regex.pattern(); + if (!rawRegex.contains("(? realMatches = new ArrayList<>(); + List> negativeLookbehindRegexes = new ArrayList<>(); + int flags = regex.flags(); + + int closePos = 0; + int startPos = rawRegex.indexOf("(?= 0) { + closePos = getClosePos(rawRegex, startPos); + Pattern nlbRegex = Pattern.compile(rawRegex.substring(startPos, closePos + 1), flags); + String nextRegex = getNextRegex(rawRegex, startPos); + + negativeLookbehindRegexes.add(Pair.with(nlbRegex, nextRegex != null ? Pattern.compile(nextRegex, flags) : null)); + + rawRegex = rawRegex.substring(0, startPos) + rawRegex.substring(closePos + 1); + startPos = rawRegex.indexOf("(? { + + AtomicBoolean isClean = new AtomicBoolean(true); + negativeLookbehindRegexes.forEach((pair) -> { + + Pattern currRegex = pair.getValue0(); + Match[] negativeLookbehindMatches = getMatchesSimple(currRegex, source); + Arrays.stream(negativeLookbehindMatches).forEach(negativeLookbehindMatch -> { + + int negativeLookbehindEnd = negativeLookbehindMatch.index + negativeLookbehindMatch.length; + Pattern nextRegex = pair.getValue1(); + + if (match.index == negativeLookbehindEnd) { + + if (nextRegex == null) { + isClean.set(false); + return; + } else { + Match nextMatch = getFirstMatchIndex(nextRegex, source.substring(negativeLookbehindMatch.index)); + if (nextMatch != null && ((nextMatch.index == negativeLookbehindMatch.length) || (source.contains(nextMatch.value + match.value)))) { + isClean.set(false); + return; + } + + } + } + + if (negativeLookbehindMatch.value.contains(match.value)) { + + Match[] preMatches = getMatchesSimple(regex, source.substring(0, match.index)); + Arrays.stream(preMatches).forEach(preMatch -> { + if (source.contains(preMatch.value + match.value)) { + isClean.set(false); + return; + } + }); + } + }); + + if (!isClean.get()) { + return; + } + }); + + if (isClean.get()) { + realMatches.add(match); + } + }); + + return realMatches.toArray(new Match[realMatches.size()]); + } + + private static String sanitizeGroups(String source) { + + String result = source; + + AtomicInteger index = new AtomicInteger(0); + result = replace(result, matchGroup, (Matcher m) -> m.group(0).replace(m.group(1), m.group(1).replace("_", "ii") + groupNameIndexSep + index.getAndIncrement())); + + index.set(0); + result = replace(result, matchPositiveLookbehind, (Matcher m) -> String.format("(?", groupNameIndexSep, index.getAndIncrement())); + + index.set(0); + result = replace(result, matchNegativeLookbehind, (Matcher m) -> String.format("(?", groupNameIndexSep, index.getAndIncrement())); + + return result; + } + + public static Pattern getSafeLookbehindRegExp(String source) { + return getSafeLookbehindRegExp(source, 0); + } + + public static Pattern getSafeLookbehindRegExp(String source, int flags) { + + String result = source; + + // Java pre 1.9 doesn't support unbounded lookbehind lengths + if (unboundedLookBehindNotSupported) { + result = bindLookbehinds(result); + } + + return Pattern.compile(result, flags); + } + + private static String bindLookbehinds(String regex) { + + String result = regex; + Stack replaceStack = new Stack<>(); + + Matcher matcher = lookBehindCheckRegex.matcher(regex); + + while (matcher.find()) { + getReplaceIndexes(result, matcher.start(), replaceStack); + } + + if (!replaceStack.empty()) { + + StringBuilder buffer = new StringBuilder(result); + while (!replaceStack.isEmpty()) { + int idx = replaceStack.peek(); + buffer.replace(idx, idx + 1, bindings.get(result.charAt(idx))); + replaceStack.pop(); + } + + result = buffer.toString(); + } + + return result; + } + + private static void getReplaceIndexes(String input, int startIndex, Stack replaceStack) { + + int idx = startIndex + 3; + Stack stack = new Stack<>(); + + while (idx < input.length()) { + switch (input.charAt(idx)) { + case ')': + if (stack.isEmpty()) { + idx = input.length(); + } else { + stack.pop(); + } + break; + case '(': + stack.push('('); + break; + case '*': + case '+': + replaceStack.push(idx); + break; + case '|': + if (stack.isEmpty()) { + idx = input.length(); + } + break; + default: + break; + } + + idx += 1; + } + } + + private static Match[] getMatchesSimple(Pattern regex, String source) { + + List matches = new ArrayList<>(); + + Matcher match = regex.matcher(source); + while (match.find()) { + + List> positiveLookbehinds = new ArrayList<>(); + Map groups = new HashMap<>(); + AtomicReference lastGroup = new AtomicReference<>(""); + + getNamedGroups(match).forEach((key, groupValue) -> { + + if (!key.contains(groupNameIndexSep)) { + return; + } + + if (key.startsWith("plb") && !StringUtility.isNullOrEmpty(match.group(key))) { + + if (match.group(0).indexOf(match.group(key)) != 0 && !StringUtility.isNullOrEmpty(lastGroup.get())) { + + int index = match.start() + match.group(0).indexOf(match.group(key)); + int length = match.group(key).length(); + String value = source.substring(index, index + length); + + MatchGroup lastMatchGroup = groups.get(lastGroup.get()); + groups.replace(lastGroup.get(), new MatchGroup( + lastMatchGroup.value + value, + lastMatchGroup.index, + lastMatchGroup.length, + lastMatchGroup.captures)); + } + + positiveLookbehinds.add(Pair.with(key, match.group(key))); + return; + } + + if (key.startsWith("nlb")) { + return; + } + + String groupKey = key.substring(0, key.lastIndexOf(groupNameIndexSep)).replace("ii", "_"); + lastGroup.set(groupKey); + + if (!groups.containsKey(groupKey)) { + groups.put(groupKey, new MatchGroup("", 0, 0, new Capture[0])); + } + + if (!StringUtility.isNullOrEmpty(match.group(key))) { + + int index = match.start(key); + int length = match.group(key).length(); + String value = source.substring(index, match.end(key)); + List captures = new ArrayList<>(Arrays.asList(groups.get(groupKey).captures)); + captures.add(new Capture(value, index, length)); + + groups.replace(groupKey, new MatchGroup(value, index, length, captures.toArray(new Capture[0]))); + } + }); + + String value = match.group(0); + int index = match.start(); + int length = value.length(); + + if (positiveLookbehinds.size() > 0 && value.indexOf(positiveLookbehinds.get(0).getValue1()) == 0) { + int valueLength = positiveLookbehinds.get(0).getValue1().length(); + value = source.substring(index, index + length).substring(valueLength); + index += valueLength; + length -= valueLength; + } else { + value = source.substring(index, index + length); + } + + matches.add(new Match(index, length, value, groups)); + } + + return matches.toArray(new Match[matches.size()]); + } + + private static Match getFirstMatchIndex(Pattern regex, String source) { + + Match[] matches = getMatches(regex, source); + if (matches.length > 0) { + return matches[0]; + } + + return null; + } + + private static String getNextRegex(String source, int startPos) { + + startPos = getClosePos(source, startPos) + 1; + int closePos = getClosePos(source, startPos); + if (source.charAt(startPos) != '(') { + closePos--; + } + + String next = (startPos == closePos) ? + null : + source.substring(startPos, closePos + 1); + + return next; + } + + private static int getClosePos(String rawRegex, int startPos) { + + int counter = 1; + int closePos = startPos; + + while (counter > 0 && closePos < rawRegex.length()) { + + ++closePos; + if (closePos < rawRegex.length()) { + char c = rawRegex.charAt(closePos); + if (c == '(') { + counter++; + } else if (c == ')') { + counter--; + } + } + } + + return closePos; + } + + public static String replace(String input, Pattern regex, StringReplacerCallback callback) { + + StringBuffer resultString = new StringBuffer(); + Matcher regexMatcher = regex.matcher(input); + + while (regexMatcher.find()) { + String replacement = callback.replace(regexMatcher); + regexMatcher.appendReplacement(resultString, replacement); + } + + regexMatcher.appendTail(resultString); + + return resultString.toString(); + } + + // Checks if Java version is <= 8, as they don't support look-behind groups with no maximum length. + private static boolean isRestrictedJavaVersion() { + + boolean result = false; + BigDecimal targetVersion = new BigDecimal("1.8"); + + try { + String specVersion = System.getProperty("java.specification.version"); + result = new BigDecimal(specVersion).compareTo(targetVersion) >= 0; + } catch (Exception e1) { + + try { + // Could also be "java.runtime.version". + String runtimeVersion = System.getProperty("java.version"); + result = new BigDecimal(runtimeVersion).compareTo(targetVersion) >= 0; + + } catch (Exception e2) { + // Nothing to do, ignore. + } + + } + + if (result) { + System.out.println("WARN: Look-behind groups with no maximum length not supported. Java version <= 8."); + } + + return result; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java new file mode 100644 index 000000000..0052865e8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.utilities; + +import java.util.regex.Matcher; + +public interface StringReplacerCallback { + public String replace(Matcher match); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java new file mode 100644 index 000000000..1d443908d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.utilities; + +public abstract class StringUtility { + public static boolean isNullOrEmpty(String source) { + return source == null || source.equals(""); + } + + public static boolean isNullOrWhiteSpace(String source) { + return source == null || source.trim().equals(""); + } + + public static String trimStart(String source) { + return source.replaceFirst("^\\s+", ""); + } + + public static String trimEnd(String source) { + return source.replaceFirst("\\s+$", ""); + } + + public static String format(double d) { + if (d == (long)d) { + return String.format("%d", (long)d); + } + + return String.format("%s", d); + } +} diff --git a/libraries/bot-dialogs/src/main/resources/naughtyStrings.txt b/libraries/bot-dialogs/src/main/resources/naughtyStrings.txt new file mode 100644 index 000000000..a89c7a42b --- /dev/null +++ b/libraries/bot-dialogs/src/main/resources/naughtyStrings.txt @@ -0,0 +1,742 @@ +# Reserved Strings +# +# Strings which may be used elsewhere in code + +undefined +undef +null +NULL +(null) +nil +NIL +true +false +True +False +TRUE +FALSE +None +hasOwnProperty +then +constructor +\ +\\ + +# Numeric Strings +# +# Strings which can be interpreted as numeric + +0 +1 +1.00 +$1.00 +1/2 +1E2 +1E02 +1E+02 +-1 +-1.00 +-$1.00 +-1/2 +-1E2 +-1E02 +-1E+02 +1/0 +0/0 +-2147483648/-1 +-9223372036854775808/-1 +-0 +-0.0 ++0 ++0.0 +0.00 +0..0 +. +0.0.0 +0,00 +0,,0 +, +0,0,0 +0.0/0 +1.0/0.0 +0.0/0.0 +1,0/0,0 +0,0/0,0 +--1 +- +-. +-, +999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +NaN +Infinity +-Infinity +INF +1#INF +-1#IND +1#QNAN +1#SNAN +1#IND +0x0 +0xffffffff +0xffffffffffffffff +0xabad1dea +123456789012345678901234567890123456789 +1,000.00 +1 000.00 +1'000.00 +1,000,000.00 +1 000 000.00 +1'000'000.00 +1.000,00 +1 000,00 +1'000,00 +1.000.000,00 +1 000 000,00 +1'000'000,00 +01000 +08 +09 +2.2250738585072011e-308 + +# Special Characters +# +# ASCII punctuation. All of these characters may need to be escaped in some +# contexts. Divided into three groups based on (US-layout) keyboard position. + +,./;'[]\-= +<>?:"{}|_+ +!@#$%^&*()`~ + +# Non-whitespace C0 controls: U+0001 through U+0008, U+000E through U+001F, +# and U+007F (DEL) +# Often forbidden to appear in various text-based file formats (e.g. XML), +# or reused for internal delimiters on the theory that they should never +# appear in input. +# The next line may appear to be blank or mojibake in some viewers. + + +# Non-whitespace C1 controls: U+0080 through U+0084 and U+0086 through U+009F. +# Commonly misinterpreted as additional graphic characters. +# The next line may appear to be blank, mojibake, or dingbats in some viewers. +€‚ƒ„†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ + +# Whitespace: all of the characters with category Zs, Zl, or Zp (in Unicode +# version 8.0.0), plus U+0009 (HT), U+000B (VT), U+000C (FF), U+0085 (NEL), +# and U+200B (ZERO WIDTH SPACE), which are in the C categories but are often +# treated as whitespace in some contexts. +# This file unfortunately cannot express strings containing +# U+0000, U+000A, or U+000D (NUL, LF, CR). +# The next line may appear to be blank or mojibake in some viewers. +# The next line may be flagged for "trailing whitespace" in some viewers. + …             ​

    + +# Unicode additional control characters: all of the characters with +# general category Cf (in Unicode 8.0.0). +# The next line may appear to be blank or mojibake in some viewers. +­؀؁؂؃؄؅؜۝܏᠎​‌‍‎‏‪‫‬‭‮⁠⁡⁢⁣⁤⁦⁧⁨⁩𑂽𛲠𛲡𛲢𛲣𝅳𝅴𝅵𝅶𝅷𝅸𝅹𝅺󠀁󠀠󠀡󠀢󠀣󠀤󠀥󠀦󠀧󠀨󠀩󠀪󠀫󠀬󠀭󠀮󠀯󠀰󠀱󠀲󠀳󠀴󠀵󠀶󠀷󠀸󠀹󠀺󠀻󠀼󠀽󠀾󠀿󠁀󠁁󠁂󠁃󠁄󠁅󠁆󠁇󠁈󠁉󠁊󠁋󠁌󠁍󠁎󠁏󠁐󠁑󠁒󠁓󠁔󠁕󠁖󠁗󠁘󠁙󠁚󠁛󠁜󠁝󠁞󠁟󠁠󠁡󠁢󠁣󠁤󠁥󠁦󠁧󠁨󠁩󠁪󠁫󠁬󠁭󠁮󠁯󠁰󠁱󠁲󠁳󠁴󠁵󠁶󠁷󠁸󠁹󠁺󠁻󠁼󠁽󠁾󠁿 + +# "Byte order marks", U+FEFF and U+FFFE, each on its own line. +# The next two lines may appear to be blank or mojibake in some viewers. + +￾ + +# Unicode Symbols +# +# Strings which contain common unicode symbols (e.g. smart quotes) + +Ω≈ç√∫˜µ≤≥÷ +åß∂ƒ©˙∆˚¬…æ +œ∑´®†¥¨ˆøπ“‘ +¡™£¢∞§¶•ªº–≠ +¸˛Ç◊ı˜Â¯˘¿ +ÅÍÎÏ˝ÓÔÒÚÆ☃ +Œ„´‰ˇÁ¨ˆØ∏”’ +`⁄€‹›fifl‡°·‚—± +⅛⅜⅝⅞ +ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя +٠١٢٣٤٥٦٧٨٩ + +# Unicode Subscript/Superscript/Accents +# +# Strings which contain unicode subscripts/superscripts; can cause rendering issues + +⁰⁴⁵ +₀₁₂ +⁰⁴⁵₀₁₂ +ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ + +# Quotation Marks +# +# Strings which contain misplaced quotation marks; can cause encoding errors + +' +" +'' +"" +'"' +"''''"'" +"'"'"''''" + + + + + +# Two-Byte Characters +# +# Strings which contain two-byte characters: can cause rendering issues or character-length issues + +田中さんにあげて下さい +パーティーへ行かないか +和製漢語 +部落格 +사회과학원 어학연구소 +찦차를 타고 온 펲시맨과 쑛다리 똠방각하 +社會科學院語學研究所 +울란바토르 +𠜎𠜱𠝹𠱓𠱸𠲖𠳏 + +# Strings which contain two-byte letters: can cause issues with naïve UTF-16 capitalizers which think that 16 bits == 1 character + +𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆 + +# Special Unicode Characters Union +# +# A super string recommended by VMware Inc. Globalization Team: can effectively cause rendering issues or character-length issues to validate product globalization readiness. +# +# 表 CJK_UNIFIED_IDEOGRAPHS (U+8868) +# ポ KATAKANA LETTER PO (U+30DD) +# あ HIRAGANA LETTER A (U+3042) +# A LATIN CAPITAL LETTER A (U+0041) +# 鷗 CJK_UNIFIED_IDEOGRAPHS (U+9DD7) +# Œ LATIN SMALL LIGATURE OE (U+0153) +# é LATIN SMALL LETTER E WITH ACUTE (U+00E9) +# B FULLWIDTH LATIN CAPITAL LETTER B (U+FF22) +# 逍 CJK_UNIFIED_IDEOGRAPHS (U+900D) +# Ü LATIN SMALL LETTER U WITH DIAERESIS (U+00FC) +# ß LATIN SMALL LETTER SHARP S (U+00DF) +# ª FEMININE ORDINAL INDICATOR (U+00AA) +# ą LATIN SMALL LETTER A WITH OGONEK (U+0105) +# ñ LATIN SMALL LETTER N WITH TILDE (U+00F1) +# 丂 CJK_UNIFIED_IDEOGRAPHS (U+4E02) +# 㐀 CJK Ideograph Extension A, First (U+3400) +# 𠀀 CJK Ideograph Extension B, First (U+20000) + +表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀 + +# Changing length when lowercased +# +# Characters which increase in length (2 to 3 bytes) when lowercased +# Credit: https://twitter.com/jifa/status/625776454479970304 + +Ⱥ +Ⱦ + +# Japanese Emoticons +# +# Strings which consists of Japanese-style emoticons which are popular on the web + +ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ +(。◕ ∀ ◕。) +`ィ(´∀`∩ +__ロ(,_,*) +・( ̄∀ ̄)・:*: +゚・✿ヾ╲(。◕‿◕。)╱✿・゚ +,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’ +(╯°□°)╯︵ ┻━┻) +(ノಥ益ಥ)ノ ┻━┻ +┬─┬ノ( º _ ºノ) +( ͡° ͜ʖ ͡°) +¯\_(ツ)_/¯ + +# Emoji +# +# Strings which contain Emoji; should be the same behavior as two-byte characters, but not always + +😍 +👩🏽 +👨‍🦰 👨🏿‍🦰 👨‍🦱 👨🏿‍🦱 🦹🏿‍♂️ +👾 🙇 💁 🙅 🙆 🙋 🙎 🙍 +🐵 🙈 🙉 🙊 +❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙 +✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿 +👨‍👩‍👦 👨‍👩‍👧‍👦 👨‍👨‍👦 👩‍👩‍👧 👨‍👦 👨‍👧‍👦 👩‍👦 👩‍👧‍👦 +🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧 +0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 + +# Regional Indicator Symbols +# +# Regional Indicator Symbols can be displayed differently across +# fonts, and have a number of special behaviors + +🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸 +🇺🇸🇷🇺🇸🇦🇫🇦🇲 +🇺🇸🇷🇺🇸🇦 + +# Unicode Numbers +# +# Strings which contain unicode numbers; if the code is localized, it should see the input as numeric + +123 +١٢٣ + +# Right-To-Left Strings +# +# Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew) + +ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو. +בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ +הָיְתָהtestالصفحات التّحول +﷽ +ﷺ +مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ، +الكل في المجمو عة (5) + +# Ogham Text +# +# The only unicode alphabet to use a space which isn't empty but should still act like a space. + +᚛ᚄᚓᚐᚋᚒᚄ ᚑᚄᚂᚑᚏᚅ᚜ +᚛                 ᚜ + +# Trick Unicode +# +# Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf) + +‪‪test‪ +‫test‫ +
test
 +test⁠test‫ +⁦test⁧ + +# Zalgo Text +# +# Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net) + +Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣ +̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰ +̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟ +̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕ +Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮ + +# Unicode Upsidedown +# +# Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com) + +˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥ +00˙Ɩ$- + +# Unicode font +# +# Strings which contain bold/italic/etc. versions of normal characters + +The quick brown fox jumps over the lazy dog +𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠 +𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌 +𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈 +𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰 +𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘 +𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐 +⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢ + +# Script Injection +# +# Strings which attempt to invoke a benign script injection; shows vulnerability to XSS + + +<script>alert('123');</script> + + +"> +'> +> + +< / script >< script >alert(123)< / script > + onfocus=JaVaSCript:alert(123) autofocus +" onfocus=JaVaSCript:alert(123) autofocus +' onfocus=JaVaSCript:alert(123) autofocus +<script>alert(123)</script> +ript>alert(123)ript> +--> +";alert(123);t=" +';alert(123);t=' +JavaSCript:alert(123) +;alert(123); +src=JaVaSCript:prompt(132) +">javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +'`"><\x3Cscript>javascript:alert(1) +'`"><\x00script>javascript:alert(1) +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +XXX + + + +<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>"> +<!--[if]><script>javascript:alert(1)</script --> +<!--[if<img src=x onerror=javascript:alert(1)//]> --> +<script src="/\%(jscript)s"></script> +<script src="\\%(jscript)s"></script> +<IMG """><SCRIPT>alert("XSS")</SCRIPT>"> +<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> +<IMG SRC=# onmouseover="alert('xxs')"> +<IMG SRC= onmouseover="alert('xxs')"> +<IMG onmouseover="alert('xxs')"> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out +<IMG SRC="  javascript:alert('XSS');"> +<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")> +<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<<SCRIPT>alert("XSS");//<</SCRIPT> +<SCRIPT SRC=http://ha.ckers.org/xss.js?< B > +<SCRIPT SRC=//ha.ckers.org/.j> +<IMG SRC="javascript:alert('XSS')" +<iframe src=http://ha.ckers.org/scriptlet.html < +\";alert('XSS');// +<u oncopy=alert()> Copy me</u> +<i onwheel=alert(1)> Scroll over me </i> +<plaintext> +http://a/%%30%30 +</textarea><script>alert(123)</script> + +# SQL Injection +# +# Strings which can cause a SQL injection if inputs are not sanitized + +1;DROP TABLE users +1'; DROP TABLE users-- 1 +' OR 1=1 -- 1 +' OR '1'='1 +'; EXEC sp_MSForEachTable 'DROP TABLE ?'; -- + +% +_ + +# Server Code Injection +# +# Strings which can cause user to run code on server as a privileged user (c.f. https://news.ycombinator.com/item?id=7665153) + +- +-- +--version +--help +$USER +/dev/null; touch /tmp/blns.fail ; echo +`touch /tmp/blns.fail` +$(touch /tmp/blns.fail) +@{[system "touch /tmp/blns.fail"]} + +# Command Injection (Ruby) +# +# Strings which can call system commands within Ruby/Rails applications + +eval("puts 'hello world'") +System("ls -al /") +`ls -al /` +Kernel.exec("ls -al /") +Kernel.exit(1) +%x('ls -al /') + +# XXE Injection (XML) +# +# String which can reveal system files when parsed by a badly configured XML parser + +<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo> + +# Unwanted Interpolation +# +# Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string. + +$HOME +$ENV{'HOME'} +%d +%s%s%s%s%s +{0} +%*.*s +%@ +%n +File:/// + +# File Inclusion +# +# Strings which can cause user to pull in files that should not be a part of a web server + +../../../../../../../../../../../etc/passwd%00 +../../../../../../../../../../../etc/hosts + +# Known CVEs and Vulnerabilities +# +# Strings that test for known vulnerabilities + +() { 0; }; touch /tmp/blns.shellshock1.fail; +() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; } +<<< %s(un='%s') = %u ++++ATH0 + +# MSDOS/Windows Special Filenames +# +# Strings which are reserved characters in MSDOS/Windows + +CON +PRN +AUX +CLOCK$ +NUL +A: +ZZ: +COM1 +LPT1 +LPT2 +LPT3 +COM2 +COM3 +COM4 + +# IRC specific strings +# +# Strings that may occur on IRC clients that make security products freak out + +DCC SEND STARTKEYLOGGER 0 0 0 + +# Scunthorpe Problem +# +# Innocuous strings which may be blocked by profanity filters (https://en.wikipedia.org/wiki/Scunthorpe_problem) + +Scunthorpe General Hospital +Penistone Community Church +Lightwater Country Park +Jimmy Clitheroe +Horniman Museum +shitake mushrooms +RomansInSussex.co.uk +http://www.cum.qc.ca/ +Craig Cockburn, Software Specialist +Linda Callahan +Dr. Herman I. Libshitz +magna cum laude +Super Bowl XXX +medieval erection of parapets +evaluate +mocha +expression +Arsenal canal +classic +Tyson Gay +Dick Van Dyke +basement + +# Human injection +# +# Strings which may cause human to reinterpret worldview + +If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you. + +# Terminal escape codes +# +# Strings which punish the fools who use cat/type on this file + +Roses are red, violets are blue. Hope you enjoy terminal hue +But now...for my greatest trick... +The quick brown fox... [Beeeep] + +# iOS Vulnerabilities +# +# Strings which crashed iMessage in various versions of iOS + +Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗 +🏳0🌈️ +జ్ఞ‌ా + +# Persian special characters +# +# This is a four characters string which includes Persian special characters (گچپژ) + +گچپژ + +# jinja2 injection +# +# first one is supposed to raise "MemoryError" exception +# second, obviously, prints contents of /etc/passwd + +{% print 'x' * 64 * 1024**3 %} +{{ "".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java new file mode 100644 index 000000000..2b125f4f3 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java @@ -0,0 +1,557 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; +import com.microsoft.bot.dialogs.prompts.PromptOptions; + + +import org.junit.Assert; +import org.junit.Test; + +public class ComponentDialogTests { + + @Test + public void CallDialogInParentComponent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + // DialogState state = dialogState.get(turnContext, () -> new + // DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + + ComponentDialog childComponent = new ComponentDialog("childComponent"); + + class Step1 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("Child started.").join(); + return stepContext.beginDialog("parentDialog", "test"); + } + } + + class Step2 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext() + .sendActivity(String.format("Child finished. Value: %s", stepContext.getResult())); + return stepContext.endDialog(); + } + } + + WaterfallStep[] childStep = new WaterfallStep[] {new Step1(), new Step2() }; + + childComponent.addDialog(new WaterfallDialog("childDialog", Arrays.asList(childStep))); + + ComponentDialog parentComponent = new ComponentDialog("parentComponent"); + parentComponent.addDialog(childComponent); + + class ParentStep implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(String.format("Parent called.", stepContext.getResult())); + return stepContext.endDialog(stepContext.getOptions()); + } + } + WaterfallStep[] parentStep = new WaterfallStep[] {new ParentStep() }; + + parentComponent.addDialog(new WaterfallDialog("parentDialog", Arrays.asList(parentStep))); + + dialogs.add(parentComponent); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("parentComponent", null).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + + return CompletableFuture.completedFuture(null); + }).send("Hi").assertReply("Child started.").assertReply("Parent called.") + .assertReply("Child finished. Value: test").startTest().join(); + } + + @Test + public void BasicWaterfallTest() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("BasicWaterfallTest", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(createWaterfall()); + try { + dialogs.add(new NumberPrompt<Integer>("number", Integer.class)); + } catch (UnsupportedDataTypeException e) { + e.printStackTrace(); + } + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("test-waterfall", null); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Thanks for '42'") + .assertReply("Enter another number.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void TelemetryBasicWaterfallTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.setTelemetryClient(new MyBotTelemetryClient()); + Assert.assertEquals(MyBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + } + + @Test + public void TelemetryHeterogeneousLoggerTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.findDialog("test-waterfall").setTelemetryClient(new MyBotTelemetryClient()); + + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + } + + @Test + public void TelemetryAddWaterfallTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.setTelemetryClient(new MyBotTelemetryClient()); + testComponentDialog.addDialog(new WaterfallDialog("C", null)); + + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("C").getTelemetryClient().getClass()); + } + + @Test + public void TelemetryNullUpdateAfterAddTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.setTelemetryClient(new MyBotTelemetryClient()); + testComponentDialog.addDialog(new WaterfallDialog("C", null)); + + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("C").getTelemetryClient().getClass()); + testComponentDialog.setTelemetryClient(null); + + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("C").getTelemetryClient().getClass()); + } + + @Test + public void BasicComponentDialogTest() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("BasicComponentDialogTest", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + try { + dialogs.add(new TestComponentDialog()); + } catch (UnsupportedDataTypeException e) { + e.printStackTrace(); + } + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("TestComponentDialog", null); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Thanks for '42'") + .assertReply("Enter another number.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void NestedComponentDialogTest() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("BasicComponentDialogTest", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + + try { + dialogs.add(new TestNestedComponentDialog()); + } catch (UnsupportedDataTypeException e) { + e.printStackTrace(); + } + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("TestNestedComponentDialog", null); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + + // step 1 + .assertReply("Enter a number.") + + // step 2 + .send("42") + .assertReply("Thanks for '42'") + .assertReply("Enter another number.") + + // step 3 and step 1 again (nested component) + .send("64") + .assertReply("Got '64'.") + .assertReply("Enter a number.") + + // step 2 again (from the nested component) + .send("101") + .assertReply("Thanks for '101'") + .assertReply("Enter another number.") + + // driver code + .send("5") + .assertReply("Bot received the number '5'.") + .startTest() + .join(); + } + + @Test + public void CallDialogDefinedInParentComponent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + Map<String, String> options = new HashMap<String, String>(); + options.put("value", "test"); + + ComponentDialog childComponent = new ComponentDialog("childComponent"); + + class Step1 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("Child started."); + return stepContext.beginDialog("parentDialog", options); + } + } + + class Step2 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + Assert.assertEquals("test", (String) stepContext.getResult()); + stepContext.getContext().sendActivity("Child finished."); + return stepContext.endDialog(); + } + } + + WaterfallStep[] childActions = new WaterfallStep[] {new Step1(), new Step2() }; + + childComponent.addDialog(new WaterfallDialog("childDialog", Arrays.asList(childActions))); + + ComponentDialog parentComponent = new ComponentDialog("parentComponent"); + parentComponent.addDialog(childComponent); + + class ParentAction implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + Map<String, String> stepOptions = (Map<String, String>) stepContext.getOptions(); + Assert.assertNotNull(stepOptions); + Assert.assertTrue(stepOptions.containsKey("value")); + stepContext.getContext().sendActivity( + String.format("Parent called with: {%s}", stepOptions.get("value"))); + return stepContext.endDialog(stepOptions.get("value")); + } + } + + WaterfallStep[] parentActions = new WaterfallStep[] { + new ParentAction() + }; + + parentComponent.addDialog(new WaterfallDialog("parentDialog", Arrays.asList(parentActions))); + new TestFlow(adapter, (turnContext) -> { + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(parentComponent); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("parentComponent", null).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + turnContext.sendActivity(MessageFactory.text("Done")); + } + return CompletableFuture.completedFuture(null); + }) + .send("Hi") + .assertReply("Child started.") + .assertReply("Parent called with: test") + .assertReply("Child finished.") + .startTest(); + } + + // private static TestFlow CreateTestFlow(WaterfallDialog waterfallDialog) { + // var convoState = new ConversationState(new MemoryStorage()); + // var dialogState = convoState.CreateProperty<DialogState>("dialogState"); + + // var adapter = new TestAdapter() + // .Use(new AutoSaveStateMiddleware(convoState)); + + // var testFlow = new TestFlow(adapter, (turnContext) -> { + // var state = dialogState.Get(turnContext, () -> new DialogState()); + // var dialogs = new DialogSet(dialogState); + + // dialogs.Add(new CancelledComponentDialog(waterfallDialog)); + + // var dc = dialogs.CreateContext(turnContext); + + // var results = dc.ContinueDialog(cancellationToken); + // if (results.Status == DialogTurnStatus.Empty) { + // results = dc.BeginDialog("TestComponentDialog", null); + // } + + // if (results.Status == DialogTurnStatus.Cancelled) { + // turnContext.SendActivity(MessageFactory.Text($"Component dialog cancelled (result value is {results.Result?.toString()}).")); + // } else if (results.Status == DialogTurnStatus.Complete) { + // var value = (int)results.Result; + // turnContext.SendActivity(MessageFactory.Text($"Bot received the number '{value}'.")); + // } + // }); + // return testFlow; + // } + + private static WaterfallDialog createWaterfall() { + class WaterfallStep1 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + return stepContext.prompt("number", options); + } + } + + class WaterfallStep2 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext() + .sendActivity(MessageFactory.text(String.format("Thanks for '%d'", numberResult))).join(); + } + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter another number.")); + return stepContext.prompt("number", options); + } + } + + WaterfallStep[] steps = new WaterfallStep[] {new WaterfallStep1(), new WaterfallStep2() }; + return new WaterfallDialog("test-waterfall", Arrays.asList(steps)); + } + + private class MyBotTelemetryClient implements BotTelemetryClient { + private MyBotTelemetryClient() { + } + + @Override + public void trackAvailability(String name, OffsetDateTime timeStamp, Duration duration, String runLocation, + boolean success, String message, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void trackDependency(String dependencyTypeName, String target, String dependencyName, String data, + OffsetDateTime startTime, Duration duration, String resultCode, boolean success) { + + } + + @Override + public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) { + + } + + @Override + public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void flush() { + + } + } + + private class TestComponentDialog extends ComponentDialog { + private TestComponentDialog() throws UnsupportedDataTypeException { + super("TestComponentDialog"); + addDialog(createWaterfall()); + addDialog(new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, Integer.class)); + } + } + + private final class TestNestedComponentDialog extends ComponentDialog { + private TestNestedComponentDialog() throws UnsupportedDataTypeException { + super("TestNestedComponentDialog"); + WaterfallStep[] steps = new WaterfallStep[] { + new WaterfallStep1(), + new WaterfallStep2(), + new WaterfallStep3(), + }; + addDialog(new WaterfallDialog("test-waterfall", Arrays.asList(steps))); + addDialog(new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, Integer.class)); + addDialog(new TestComponentDialog()); + } + class WaterfallStep1 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + return stepContext.prompt("number", options); + } + } + + class WaterfallStep2 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext() + .sendActivity(MessageFactory.text(String.format("Thanks for '%d'", numberResult))).join(); + } + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter another number.")); + return stepContext.prompt("number", options); + } + } + class WaterfallStep3 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext().sendActivity( + MessageFactory.text(String.format("Got '%d'.", numberResult))); + } + return stepContext.beginDialog("TestComponentDialog", null); + } + } + + } + + // private class CancelledComponentDialog : ComponentDialog { + // public CancelledComponentDialog(Dialog waterfallDialog) { + // super("TestComponentDialog"); + // AddDialog(waterfallDialog); + // AddDialog(new NumberPrompt<int>("number", defaultLocale: Culture.English)); + // } + // } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java new file mode 100644 index 000000000..a03613d3f --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.concurrent.CompletableFuture; + +import org.junit.Assert; +import org.junit.Test; + +public class DialogContainerTests { + + @Test + public void DialogContainer_GetVersion() { + TestContainer ds = new TestContainer(); + String version1 = ds.getInternalVersion_Test(); + Assert.assertNotNull(version1); + + TestContainer ds2 = new TestContainer(); + String version2 = ds.getInternalVersion_Test(); + Assert.assertNotNull(version2); + Assert.assertEquals(version1, version2); + + DialogTestFunction testFunction = testFunction1 -> { + return CompletableFuture.completedFuture(null); + }; + LamdbaDialog ld = new LamdbaDialog("Lamdba1", testFunction); + ld.setId("A"); + + ds2.getDialogs().add(ld); + String version3 = ds2.getInternalVersion_Test(); + Assert.assertNotNull(version3); + Assert.assertNotEquals(version2, version3); + + String version4 = ds2.getInternalVersion_Test(); + Assert.assertNotNull(version3); + Assert.assertEquals(version3, version4); + + TestContainer ds3 = new TestContainer(); + DialogTestFunction testFunction2 = testFunction1 -> { + return CompletableFuture.completedFuture(null); + }; + LamdbaDialog ld2 = new LamdbaDialog("Lamdba1", testFunction2); + ld2.setId("A"); + ds3.getDialogs().add(ld2); + + String version5 = ds3.getInternalVersion_Test(); + Assert.assertNotNull(version5); + Assert.assertEquals(version5, version4); + + ds3.setProperty("foobar"); + String version6 = ds3.getInternalVersion_Test(); + Assert.assertNotNull(version6); + Assert.assertNotEquals(version6, version5); + + TestContainer ds4 = new TestContainer(); + ds4.setProperty("foobar"); + + DialogTestFunction testFunction3 = testFunction1 -> { + return CompletableFuture.completedFuture(null); + }; + LamdbaDialog ld3 = new LamdbaDialog("Lamdba1", testFunction3); + ld3.setId("A"); + + ds4.getDialogs().add(ld3); + String version7 = ds4.getInternalVersion_Test(); + Assert.assertNotNull(version7); + Assert.assertEquals(version7, version6); + } + + public class TestContainer extends DialogContainer { + + private String property; + + @Override + public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc, Object options) { + return dc.endDialog(); + } + + @Override + public DialogContext createChildContext(DialogContext dc) { + return dc; + } + + public String getInternalVersion_Test() { + return getInternalVersion(); + } + + @Override + protected String getInternalVersion() { + StringBuilder result = new StringBuilder(); + result.append(super.getInternalVersion()); + if (getProperty() != null) { + result.append(getProperty()); + } + return result.toString(); + } + + /** + * @return the Property value as a String. + */ + public String getProperty() { + return this.property; + } + + /** + * @param withProperty The Property value. + */ + public void setProperty(String withProperty) { + this.property = withProperty; + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java new file mode 100644 index 000000000..a2f19afb5 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -0,0 +1,524 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.SendActivitiesHandler; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ResourceResponse; +import com.microsoft.bot.schema.ResultPair; + +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +public class DialogManagerTests { + + // An App D for a parent bot. + private final String _parentBotId = UUID.randomUUID().toString(); + + // An App D for a skill bot. + private final String _skillBotId = UUID.randomUUID().toString(); + + // Captures an EndOfConversation if it was sent to help with assertions. + private Activity _eocSent; + + // Property to capture the DialogManager turn results and do assertions. + private DialogManagerResult _dmTurnResult; + + /** + * Enum to handle different skill test cases. + */ + public enum SkillFlowTestCase { + /** + * DialogManager is executing on a root bot with no skills (typical standalone + * bot). + */ + RootBotOnly, + + /** + * DialogManager is executing on a root bot handling replies from a skill. + */ + RootBotConsumingSkill, + + /** + * DialogManager is executing in a skill that is called from a root and calling + * another skill. + */ + MiddleSkill, + + /** + * DialogManager is executing in a skill that is called from a parent (a root or + * another skill) but doesn't call another skill. + */ + LeafSkill + } + + @Test + public void DialogManager_ConversationState_PersistedAcrossTurns() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void DialogManager_AlternateProperty() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId, "dialogState", null, null) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void DialogManager_ConversationState_ClearedAcrossConversations() { + String firstConversationId = UUID.randomUUID().toString(); + String secondConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + + CreateFlow(adaptiveDialog, storage, secondConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("John") + .assertReply("Hello John, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void DialogManager_UserState_PersistedAcrossConversations() { + String firstConversationId = UUID.randomUUID().toString(); + String secondConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("user.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + + CreateFlow(adaptiveDialog, storage, secondConversationId) + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void + DialogManager_UserState_NestedDialogs_PersistedAcrossConversations() { + String firstConversationId = UUID.randomUUID().toString(); + String secondConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog outerAdaptiveDialog = CreateTestDialog("user.name"); + + ComponentDialog componentDialog = new ComponentDialog(null); + componentDialog.addDialog(outerAdaptiveDialog); + + CreateFlow(componentDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + + CreateFlow(componentDialog, storage, secondConversationId) + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + // @Test + // public CompletableFuture<Void> DialogManager_OnErrorEvent_Leaf() { + // TestUtilities.RunTestScript(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_OnErrorEvent_Parent() { + // TestUtilities.RunTestScript(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_OnErrorEvent_Root() { + // TestUtilities.RunTestScript(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_DialogSet() { + // var storage = new MemoryStorage(); + // var convoState = new ConversationState(storage); + // var userState = new UserState(storage); + + // var adapter = new TestAdapter(); + // adapter + // .UseStorage(storage) + // .UseBotState(userState, convoState) + // .Use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger(traceActivity: false))); + + // var rootDialog = new AdaptiveDialog() { + // Triggers new ArrayList<OnCondition>() { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog>() { + // new SetProperty() { + // Property = "conversation.dialogId", + // Value = "test" + // }, + // new BeginDialog() { + // Dialog = "=conversation.dialogId" + // }, + // new BeginDialog() { + // Dialog = "test" + // } + // } + // } + // } + // }; + + // var dm = new DialogManager(rootDialog); + // dm.Dialogs.Add(new SimpleDialog() { Id = "test" }); + + // new TestFlow(adapter, (turnContext) -> { + // dm.OnTurn(turnContext: cancellationToken); + // }) + // .SendConversationUpdate() + // .assertReply("simple") + // .assertReply("simple") + // .startTest(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_ContainerRegistration() { + // var root = new AdaptiveDialog("root") { + // Triggers new ArrayList<OnCondition> { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog> { new AdaptiveDialog("inner") } + // } + // } + // }; + + // var storage = new MemoryStorage(); + // var convoState = new ConversationState(storage); + // var userState = new UserState(storage); + + // var adapter = new TestAdapter(); + // adapter + // .UseStorage(storage) + // .UseBotState(userState, convoState); + + // // The inner adaptive dialog should be registered on the DialogManager after OnTurn + // var dm = new DialogManager(root); + + // new TestFlow(adapter, (turnContext) -> { + // dm.OnTurn(turnContext: cancellationToken); + // }) + // .SendConversationUpdate() + // .startTest(); + + // Assert.NotNull(dm.Dialogs.Find("inner")); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_ContainerRegistration_DoubleNesting() { + // // Create the following dialog tree + // // Root (adaptive) -> inner (adaptive) -> innerinner(adaptive) -> helloworld (SendActivity) + // var root = new AdaptiveDialog("root") { + // Triggers new ArrayList<OnCondition> { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog> { + // new AdaptiveDialog("inner") { + // Triggers new ArrayList<OnCondition> { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog> { + // new AdaptiveDialog("innerinner") { + // Triggers new ArrayList<OnCondition>() { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog>() { + // new SendActivity("helloworld") + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + // }; + + // var storage = new MemoryStorage(); + // var convoState = new ConversationState(storage); + // var userState = new UserState(storage); + + // var adapter = new TestAdapter(); + // adapter + // .UseStorage(storage) + // .UseBotState(userState, convoState); + + // // The inner adaptive dialog should be registered on the DialogManager after OnTurn + // var dm = new DialogManager(root); + + // new TestFlow(adapter, (turnContext) -> { + // dm.OnTurn(turnContext: cancellationToken); + // }) + // .SendConversationUpdate() + // .startTest(); + + // // Top level containers should be registered + // Assert.NotNull(dm.Dialogs.Find("inner")); + + // // Mid level containers should be registered + // Assert.NotNull(dm.Dialogs.Find("innerinner")); + + // // Leaf nodes / non-contaners should not be registered + // Assert.DoesNotContain(dm.Dialogs.GetDialogs(), d -> d.GetType() == typeof(SendActivity)); + // } + + // public CompletableFuture<Void> HandlesBotAndSkillsTestCases(SkillFlowTestCase testCase, boolean shouldSendEoc) { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: testCase, locale: "en-GB").send("Hi") + // .assertReply("Hello, what is your name?") + // .send("SomeName") + // .assertReply("Hello SomeName, nice to meet you!") + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Complete, _dmTurnResult.TurnResult.Status); + + // if (shouldSendEoc) { + // Assert.NotNull(_eocSent); + // Assert.Equal(ActivityTypes.EndOfConversation, _eocSent.Type); + // Assert.Equal("SomeName", _eocSent.Value); + // Assert.Equal("en-GB", _eocSent.Locale); + // } else { + // Assert.Null(_eocSent); + // } + // } + + // @Test + // public CompletableFuture<Void> SkillHandlesEoCFromParent() { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + + // var eocActivity = new Activity(ActivityTypes.EndOfConversation); + + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) + // .send("hi") + // .assertReply("Hello, what is your name?") + // .send(eocActivity) + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Cancelled, _dmTurnResult.TurnResult.Status); + // } + + // @Test + // public CompletableFuture<Void> SkillHandlesRepromptFromParent() { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + + // var repromptEvent = new Activity(ActivityTypes.Event) { Name = DialogEvents.RepromptDialog }; + + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) + // .send("hi") + // .assertReply("Hello, what is your name?") + // .send(repromptEvent) + // .assertReply("Hello, what is your name?") + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Waiting, _dmTurnResult.TurnResult.Status); + // } + + // @Test + // public CompletableFuture<Void> SkillShouldReturnEmptyOnRepromptWithNoDialog() { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + + // var repromptEvent = new Activity(ActivityTypes.Event) { Name = DialogEvents.RepromptDialog }; + + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) + // .send(repromptEvent) + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Empty, _dmTurnResult.TurnResult.Status); + // } + + private Dialog CreateTestDialog(String property) { + return new AskForNameDialog(property.replace(".", ""), property); + } + + private TestFlow CreateFlow(Dialog dialog, Storage storage, String conversationId) { + return this.CreateFlow(dialog, storage, conversationId, null, SkillFlowTestCase.RootBotOnly, null); + } + + private TestFlow CreateFlow(Dialog dialog, Storage storage, String conversationId, String dialogStateProperty, + SkillFlowTestCase testCase, String locale) { + if (testCase == null) { + testCase = SkillFlowTestCase.RootBotOnly; + } + ConversationState convoState = new ConversationState(storage); + UserState userState = new UserState(storage); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference(conversationId, "User1", "Bot")); + adapter.useStorage(storage) + .useBotState(userState, convoState) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + if (!StringUtils.isBlank(locale)) { + adapter.setLocale(locale); + } + final SkillFlowTestCase finalTestCase = testCase; + DialogManager dm = new DialogManager(dialog, dialogStateProperty); + return new TestFlow(adapter, (turnContext) -> { + if (finalTestCase != SkillFlowTestCase.RootBotOnly) { + throw new NotImplementedException("CreateFlow is only capable of RootBotOnly test for now."); + // Create a skill ClaimsIdentity and put it in TurnState so SkillValidation.IsSkillClaim() returns true. + // ClaimsIdentity claimsIdentity = new ClaimsIdentity(); + // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); + // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _skillBotId)); + // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _parentBotId)); + // turnContext.TurnState.Add(BotAdapter.BotIdentityKey, claimsIdentity); + + // if (testCase == SkillFlowTestCase.RootBotConsumingSkill) { + // // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. + // // This emulates a response coming to a root bot through SkillHandler. + // turnContext.TurnState.Add(SkillHandler.SkillConversationReferenceKey, new SkillConversationReference { OAuthScope = AuthenticationConstants.ToChannelFromBotOAuthScope }); + // } + + // if (testCase == SkillFlowTestCase.MiddleSkill) { + // // Simulate the SkillConversationReference with a parent Bot D stored in TurnState. + // // This emulates a response coming to a skill from another skill through SkillHandler. + // turnContext.TurnState.Add(SkillHandler.SkillConversationReferenceKey, new SkillConversationReference { OAuthScope = _parentBotId }); + // } + } + + turnContext.onSendActivities(new TestSendActivities()); + + // Capture the last DialogManager turn result for assertions. + _dmTurnResult = dm.onTurn(turnContext).join(); + + return CompletableFuture.completedFuture(null); + }); + } + + class TestSendActivities implements SendActivitiesHandler { + @Override + public CompletableFuture<ResourceResponse[]> invoke(TurnContext context, List<Activity> activities, + Supplier<CompletableFuture<ResourceResponse[]>> next) { + for (Activity activity : activities) { + if (activity.getType() == ActivityTypes.END_OF_CONVERSATION) { + _eocSent = activity; + break; + } + } + return next.get(); + } + } + + private class AskForNameDialog extends ComponentDialog implements DialogDependencies { + private final String property; + + private AskForNameDialog(String id, String property) { + super(id); + addDialog(new TextPrompt("prompt")); + this.property = property; + } + + @Override + public List<Dialog> getDependencies() { + return new ArrayList<Dialog>(getDialogs().getDialogs()); + } + + @Override + public CompletableFuture<DialogTurnResult> beginDialog(DialogContext outerDc, Object options) { + + ResultPair<String> value = outerDc.getState().tryGetValue(property, String.class); + if (value.getLeft()) { + outerDc.getContext().sendActivity(String.format("Hello %s, nice to meet you!", value.getRight())); + return outerDc.endDialog(value.getRight()); + } + + PromptOptions pOptions = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Hello, what is your name?"); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Hello, what is your name?"); + pOptions.setPrompt(prompt); + pOptions.setRetryPrompt(retryPrompt); + + return outerDc.beginDialog("prompt", pOptions); + } + + @Override + public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext outerDc, + DialogReason reason, Object result) { + outerDc.getState().setValue(property, result); + outerDc.getContext().sendActivity(String.format("Hello %s, nice to meet you!", result)).join(); + return outerDc.endDialog(result); + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java new file mode 100644 index 000000000..4ba2e3253 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Map; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TestUtilities; +import com.microsoft.bot.builder.TurnContext; + +import org.apache.commons.lang3.NotImplementedException; +import org.junit.Assert; +import org.junit.Test; + +public class DialogSetTests { + + @Test + public void DialogSet_ConstructorValid() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + new DialogSet(dialogStateProperty); + } + + @Test + public void DialogSet_ConstructorNullProperty() { + Assert.assertThrows(IllegalArgumentException.class, () -> new DialogSet(null)); + } + + @Test + public void DialogSet_CreateContext() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty); + TurnContext context = TestUtilities.createEmptyContext(); + ds.createContext(context); + } + + @Test + public void DialogSet_NullCreateContext() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty); + ds.createContext(null).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void DialogSet_AddWorks() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + WaterfallDialog dialogA = new WaterfallDialog("A", null); + WaterfallDialog dialogB = new WaterfallDialog("B", null); + DialogSet ds = new DialogSet(dialogStateProperty).add(dialogA).add(dialogB); + + Assert.assertNotNull(ds.find("A")); + Assert.assertNotNull(ds.find("B")); + Assert.assertNull(ds.find("C")); + } + + @Test + public void DialogSet_GetVersion() { + DialogSet ds = new DialogSet(); + String version1 = ds.getVersion(); + Assert.assertNotNull(version1); + + DialogSet ds2 = new DialogSet(); + String version2 = ds.getVersion(); + Assert.assertNotNull(version2); + Assert.assertEquals(version1, version2); + + ds2.add(new LamdbaDialog("A", null)); + String version3 = ds2.getVersion(); + Assert.assertNotNull(version3); + Assert.assertNotEquals(version2, version3); + + String version4 = ds2.getVersion(); + Assert.assertNotNull(version3); + Assert.assertEquals(version3, version4); + + DialogSet ds3 = new DialogSet().add(new LamdbaDialog("A", null)); + + String version5 = ds3.getVersion(); + Assert.assertNotNull(version5); + Assert.assertEquals(version5, version4); + } + + @Test + public void DialogSet_TelemetrySet() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty).add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + + MyBotTelemetryClient botTelemetryClient = new MyBotTelemetryClient(); + ds.setTelemetryClient(botTelemetryClient); + + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + } + + @Test + public void DialogSet_NullTelemetrySet() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty).add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)); + + ds.setTelemetryClient(null); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + } + + @Test + public void DialogSet_AddTelemetrySet() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty).add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)); + + ds.setTelemetryClient(new MyBotTelemetryClient()); + ds.add(new WaterfallDialog("C", null)); + + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("C").getTelemetryClient().getClass().getSimpleName()); + } + + @Test + public void DialogSet_HeterogeneousLoggers() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty) + .add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)) + .add(new WaterfallDialog("C", null)); + + // Make sure we can (after Adding) the TelemetryClient and "sticks" + ds.find("C").setTelemetryClient(new MyBotTelemetryClient()); + + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("C").getTelemetryClient().getClass().getSimpleName()); + } + + private final class MyBotTelemetryClient implements BotTelemetryClient { + private MyBotTelemetryClient() { + } + + @Override + public void trackAvailability(String name, OffsetDateTime timeStamp, Duration duration, String runLocation, + boolean success, String message, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackAvailability is not implemented"); + } + + @Override + public void trackDependency(String dependencyTypeName, String target, String dependencyName, String data, + OffsetDateTime startTime, Duration duration, String resultCode, boolean success) { + throw new NotImplementedException("trackDependency is not implemented"); + } + + @Override + public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackEvent is not implemented"); + } + + @Override + public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackException is not implemented"); + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) { + throw new NotImplementedException("trackTrace is not implemented"); + } + + @Override + public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackDialogView is not implemented"); + } + + @Override + public void flush() { + throw new NotImplementedException("flush is not implemented"); + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java new file mode 100644 index 000000000..ec38ca1a7 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; +import com.microsoft.bot.dialogs.memory.PathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtAtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.DollarPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.HashPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.PercentPathResolver; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.Assert; + +/** + * Test for the DialogStateManager. + */ +public class DialogStateManagerTests { + + @Rule + public TestName name = new TestName(); + + @Test + public void testMemoryScopeNullChecks() { + DialogTestFunction testFunction = dialogContext -> { + DialogStateManagerConfiguration configuration = dialogContext.getState().getConfiguration(); + for (MemoryScope scope : configuration.getMemoryScopes()) { + try { + scope.getMemory(null); + fail(String.format("Should have thrown exception with null for getMemory %s", + scope.getClass().getName())); + } catch (Exception ex) { + } + try { + scope.setMemory(null, new Object()); + fail(String.format("Should have thrown exception with null for setMemory %s", + scope.getClass().getName())); + } catch (Exception ex) { + + } + } + return CompletableFuture.completedFuture(null); + }; + + createDialogContext(testFunction).startTest().join(); + } + + @Test + public void testPathResolverNullChecks() { + DialogsComponentRegistration registration = new DialogsComponentRegistration(); + + for (PathResolver resolver : registration.getPathResolvers()) { + try { + resolver.transformPath(null); + fail(String.format( + "Should have thrown exception with null for matches()" + resolver.getClass().getName())); + + } catch (Exception ex) { + + } + } + } + + @Test + public void testMemorySnapshot() { + DialogTestFunction testFunction = dialogContext -> { + JsonNode snapshot = dialogContext.getState().getMemorySnapshot(); + DialogStateManager dsm = new DialogStateManager(dialogContext, null); + for (MemoryScope memoryScope : dsm.getConfiguration().getMemoryScopes()) { + if (memoryScope.getIncludeInSnapshot()) { + Assert.assertNotNull(snapshot.get(memoryScope.getName())); + } else { + Assert.assertNull(snapshot.get(memoryScope.getName())); + } + } + return CompletableFuture.completedFuture(null); + }; + + createDialogContext(testFunction).startTest().join(); + } + + @Test + public void testPathResolverTransform() { + // dollar tests + Assert.assertEquals("$", new DollarPathResolver().transformPath("$")); + Assert.assertEquals("$23", new DollarPathResolver().transformPath("$23")); + Assert.assertEquals("$$", new DollarPathResolver().transformPath("$$")); + Assert.assertEquals("dialog.foo", new DollarPathResolver().transformPath("$foo")); + Assert.assertEquals("dialog.foo.bar", new DollarPathResolver().transformPath("$foo.bar")); + Assert.assertEquals("dialog.foo.bar[0]", new DollarPathResolver().transformPath("$foo.bar[0]")); + + // hash tests + Assert.assertEquals("#", new HashPathResolver().transformPath("#")); + Assert.assertEquals("#23", new HashPathResolver().transformPath("#23")); + Assert.assertEquals("##", new HashPathResolver().transformPath("##")); + Assert.assertEquals("turn.recognized.intents.foo", new HashPathResolver().transformPath("#foo")); + Assert.assertEquals("turn.recognized.intents.foo.bar", new HashPathResolver().transformPath("#foo.bar")); + Assert.assertEquals("turn.recognized.intents.foo.bar[0]", new HashPathResolver().transformPath("#foo.bar[0]")); + + // @ test + Assert.assertEquals("@", new AtPathResolver().transformPath("@")); + Assert.assertEquals("@23", new AtPathResolver().transformPath("@23")); + Assert.assertEquals("@@foo", new AtPathResolver().transformPath("@@foo")); + Assert.assertEquals("turn.recognized.entities.foo.first()", new AtPathResolver().transformPath("@foo")); + Assert.assertEquals("turn.recognized.entities.foo.first().bar", new AtPathResolver().transformPath("@foo.bar")); + + // @@ teest + Assert.assertEquals("@@", new AtAtPathResolver().transformPath("@@")); + Assert.assertEquals("@@23", new AtAtPathResolver().transformPath("@@23")); + Assert.assertEquals("@@@@", new AtAtPathResolver().transformPath("@@@@")); + Assert.assertEquals("turn.recognized.entities.foo", new AtAtPathResolver().transformPath("@@foo")); + + // % config tests + Assert.assertEquals("%", new PercentPathResolver().transformPath("%")); + Assert.assertEquals("%23", new PercentPathResolver().transformPath("%23")); + Assert.assertEquals("%%", new PercentPathResolver().transformPath("%%")); + Assert.assertEquals("class.foo", new PercentPathResolver().transformPath("%foo")); + Assert.assertEquals("class.foo.bar", new PercentPathResolver().transformPath("%foo.bar")); + Assert.assertEquals("class.foo.bar[0]", new PercentPathResolver().transformPath("%foo.bar[0]")); + } + + @Test + public void testSimpleValues() { + DialogTestFunction testFunction = dc -> { + // simple value types + dc.getState().setValue("UseR.nuM", 15); + dc.getState().setValue("uSeR.NuM", 25); + Assert.assertEquals(25, (int) dc.getState().getValue("user.num", 0, Integer.class)); + + dc.getState().setValue("UsEr.StR", "string1"); + dc.getState().setValue("usER.STr", "string2"); + Assert.assertEquals("string2", dc.getState().getValue("USer.str", "", String.class)); + + // simple value types + dc.getState().setValue("ConVErsation.nuM", 15); + dc.getState().setValue("ConVErSation.NuM", 25); + Assert.assertEquals(25, (int) dc.getState().getValue("conversation.num", 0, Integer.class)); + + dc.getState().setValue("ConVErsation.StR", "string1"); + dc.getState().setValue("CoNVerSation.STr", "string2"); + Assert.assertEquals("string2", dc.getState().getValue("conversation.str", "", String.class)); + + // simple value types + dc.getState().setValue("tUrn.nuM", 15); + dc.getState().setValue("turN.NuM", 25); + Assert.assertEquals(25, (int) dc.getState().getValue("turn.num", 0, Integer.class)); + + dc.getState().setValue("tuRn.StR", "string1"); + dc.getState().setValue("TuRn.STr", "string2"); + Assert.assertEquals("string2", dc.getState().getValue("turn.str", "", String.class)); + + return CompletableFuture.completedFuture(null); + }; + + createDialogContext(testFunction).startTest().join(); + } + + @Test + public void TestEntitiesRetrieval() { + DialogTestFunction testFunction = dc -> { + + ObjectMapper mapper = new ObjectMapper(); + + String[] array = new String[] { + "test1", + "test2", + "test3" + }; + + String[] array2 = new String[] { + "testx", + "testy", + "testz" + }; + + String[][] arrayarray = new String[][] { + array2, + array + }; + + JsonNode arrayNode = mapper.valueToTree(array); + JsonNode arrayArrayNode = mapper.valueToTree(arrayarray); + + dc.getState().setValue("turn.recognized.entities.single", arrayNode); + dc.getState().setValue("turn.recognized.entities.double", arrayArrayNode); + + Assert.assertEquals("test1", dc.getState().getValue("@single", new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("@double", new String(), String.class)); + Assert.assertEquals("test1", dc.getState().getValue("turn.recognized.entities.single.First()", + new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("turn.recognized.entities.double.First()", + new String(), String.class)); + + + + // arrayarray = new JArray(); + ArrayNode secondArray = mapper.createArrayNode(); + ArrayNode array1Node = mapper.createArrayNode(); + ObjectNode node1 = mapper.createObjectNode(); + node1.put("name", "test1"); + ObjectNode node2 = mapper.createObjectNode(); + node2.put("name", "test2"); + ObjectNode node3 = mapper.createObjectNode(); + node3.put("name", "test3"); + array1Node.addAll(Arrays.asList(node1, node2, node3)); + + ArrayNode array2Node = mapper.createArrayNode(); + ObjectNode node1a = mapper.createObjectNode(); + node1a.put("name", "testx"); + ObjectNode node2a = mapper.createObjectNode(); + node2a.put("name", "testy"); + ObjectNode node3a = mapper.createObjectNode(); + node3a.put("name", "testz"); + array2Node.addAll(Arrays.asList(node1a, node2a, node3a)); + secondArray.addAll(Arrays.asList(array2Node, array1Node)); + dc.getState().setValue("turn.recognized.entities.single", array1Node); + dc.getState().setValue("turn.recognized.entities.double", secondArray); + + Assert.assertEquals("test1", dc.getState().getValue("@single.name", new String(), String.class)); + Assert.assertEquals("test1", dc.getState().getValue("turn.recognized.entities.single.First().name", + new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("@double.name", new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("turn.recognized.entities.double.First().name", + new String(), String.class)); + return CompletableFuture.completedFuture(null); + + }; + + createDialogContext(testFunction).startTest().join(); + } + + + private TestFlow createDialogContext(DialogTestFunction handler) { + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference(name.getMethodName(), "User1", "Bot")) + .useStorage(new MemoryStorage()).useBotState(new UserState(new MemoryStorage())) + .useBotState(new ConversationState(new MemoryStorage())); + + DialogManager dm = new DialogManager(new LamdbaDialog(name.getMethodName(), handler), name.getMethodName()); + // dm.getInitialTurnState().add(new ResourceExplorer()); + return new TestFlow(adapter, (turnContext -> { + dm.onTurn(turnContext); + return CompletableFuture.completedFuture(null); + })).sendConverationUpdate(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java new file mode 100644 index 000000000..e425793ab --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.concurrent.CompletableFuture; + +/** + * An inteface to run the test. + */ +interface DialogTestFunction { + /** + * Method to run the test. + * @param dc DialogContext to run the test against. + * @return a CompletableFuture + */ + CompletableFuture<Void> runTest(DialogContext dc); +} + +/** + * Dialog that can process a test. + */ +public class LamdbaDialog extends Dialog { + + private DialogTestFunction func; + + /** + * + * @param testName The name of the test being performed + * @param function The function that will perform the test. + */ + public LamdbaDialog(String testName, DialogTestFunction function) { + super(testName); + func = function; + } + + /** + * Override the beginDialog method and call our test function here. + */ + @Override + public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc, Object options) { + func.runTest(dc).join(); + return dc.endDialog(); + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java new file mode 100644 index 000000000..fbf000797 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.scopes.BotStateMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ConversationMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogContextMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ThisMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.UserMemoryScope; + +import org.javatuples.Pair; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +public class MemoryScopeTests { + + @Rule + public TestName testName = new TestName(); + + public TestFlow CreateDialogContext(DialogTestFunction handler) { + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference(testName.getMethodName(), "User1", "Bot")); + adapter.useStorage(new MemoryStorage()).useBotState(new UserState(new MemoryStorage())) + .useBotState(new ConversationState(new MemoryStorage())); + DialogManager dm = new DialogManager(new LamdbaDialog(testName.getMethodName(), handler), null); + return new TestFlow(adapter, (turnContext) -> { + return dm.onTurn(turnContext).thenAccept(null); + }).sendConverationUpdate(); + } + + @Test + public void SimpleMemoryScopesTest() { + DialogTestFunction testFunction = dc -> { + DialogStateManager dsm = dc.getState(); + for (MemoryScope memoryScope : dsm.getConfiguration().getMemoryScopes()) { + if (!(memoryScope instanceof ThisMemoryScope || memoryScope instanceof DialogMemoryScope + || memoryScope instanceof ClassMemoryScope || memoryScope instanceof DialogClassMemoryScope + || memoryScope instanceof DialogContextMemoryScope)) { + Object memory = memoryScope.getMemory(dc); + Assert.assertNotNull(memory); + ObjectPath.setPathValue(memory, "test", 15); + memory = memoryScope.getMemory(dc); + Assert.assertEquals(15, (int) ObjectPath.getPathValue(memory, "test", Integer.class)); + ObjectPath.setPathValue(memory, "test", 25); + memory = memoryScope.getMemory(dc); + Assert.assertEquals(25, (int) ObjectPath.getPathValue(memory, "test", Integer.class)); + memory = memoryScope.getMemory(dc); + ObjectPath.setPathValue(memory, "source", "destination"); + ObjectPath.setPathValue(memory, "{source}", 24); + Assert.assertEquals(24, (int) ObjectPath.getPathValue(memory, "{source}", Integer.class)); + ObjectPath.removePathValue(memory, "{source}"); + Assert.assertNull(ObjectPath.tryGetPathValue(memory, "{source}", Integer.class)); + ObjectPath.removePathValue(memory, "source"); + Assert.assertNull(ObjectPath.tryGetPathValue(memory, "{source}", Integer.class)); + } + } + return CompletableFuture.completedFuture(null); + }; + CreateDialogContext(testFunction).startTest(); + } + + @Test + public void BotStateMemoryScopeTest() { + DialogTestFunction testFunction = dc -> { + DialogStateManager dsm = dc.getState(); + Storage storage = dc.getContext().getTurnState().get(MemoryStorage.class); + UserState userState = dc.getContext().getTurnState().get(UserState.class); + ConversationState conversationState = dc.getContext().getTurnState().get(ConversationState.class); + CustomState customState = new CustomState(storage); + + dc.getContext().getTurnState().add(customState); + + List<Pair<BotState, MemoryScope>> stateScopes = new ArrayList<Pair<BotState, MemoryScope>>(); + stateScopes.add(Pair.with(userState, new UserMemoryScope())); + stateScopes.add(Pair.with(conversationState, new ConversationMemoryScope())); + stateScopes.add(Pair.with(customState, + new BotStateMemoryScope<CustomState>(CustomState.class, "CustomState"))); + + for (Pair<BotState, MemoryScope> stateScope : stateScopes) { + final String name = "test-name"; + final String value = "test-value"; + stateScope.getValue0().createProperty(name).set(dc.getContext(), value); + + Object memory = stateScope.getValue1().getMemory(dc); + + Assert.assertEquals(value, ObjectPath.getPathValue(memory, name, String.class)); + } + return CompletableFuture.completedFuture(null); + }; + CreateDialogContext(testFunction).startTest(); + } + + public class CustomState extends BotState { + public CustomState(Storage storage) { + super(storage, "Not the name of the type"); + } + + @Override + public String getStorageKey(TurnContext turnContext) { + // TODO Auto-generated method stub + return "botstate/custom/etc"; + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java new file mode 100644 index 000000000..cb6221dcf --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java @@ -0,0 +1,506 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.schema.Serialization; +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; + +public class ObjectPathTests { + @Test + public void typed_OnlyDefaultTest() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.bool = true; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options overlay = new Options() { }; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, defaultOptions.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void typed_OnlyOverlay() { + Options defaultOptions = new Options(); + + Options overlay = new Options(); + overlay.lastName = "Smith"; + overlay.firstName = "Fred"; + overlay.age = 22; + overlay.bool = true; + overlay.location = new Location(); + overlay.location.latitude = 1.2312312F; + overlay.location.longitude = 3.234234F; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, overlay.lastName); + Assert.assertEquals(result.firstName, overlay.firstName); + Assert.assertEquals(result.age, overlay.age); + Assert.assertEquals(result.bool, overlay.bool); + Assert.assertEquals(result.location.latitude, overlay.location.latitude, .01); + Assert.assertEquals(result.location.longitude, overlay.location.longitude, .01); + } + + @Test + public void typed_FullOverlay() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options overlay = new Options(); + overlay.lastName = "Grant"; + overlay.firstName = "Eddit"; + overlay.age = 32; + overlay.bool = true; + overlay.location = new Location(); + overlay.location.latitude = 2.2312312F; + overlay.location.longitude = 2.234234F; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, overlay.lastName); + Assert.assertEquals(result.firstName, overlay.firstName); + Assert.assertEquals(result.age, overlay.age); + Assert.assertEquals(result.bool, overlay.bool); + Assert.assertEquals(result.location.latitude, overlay.location.latitude, .01); + Assert.assertEquals(result.location.longitude, overlay.location.longitude, .01); + } + + @Test + public void typed_PartialOverlay() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options overlay = new Options(); + overlay.lastName = "Grant"; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, overlay.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void anonymous_OnlyDefaultTest() throws NoSuchFieldException, IllegalAccessException { + Object defaultOptions = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Options overlay = new Options() { }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.getClass().getDeclaredField("lastName").get(defaultOptions)); + Assert.assertEquals(result.firstName, defaultOptions.getClass().getDeclaredField("firstName").get(defaultOptions)); + Assert.assertEquals(result.age, defaultOptions.getClass().getDeclaredField("age").get(defaultOptions)); + Assert.assertEquals(result.bool, defaultOptions.getClass().getDeclaredField("bool").get(defaultOptions)); + + Object loc = defaultOptions.getClass().getDeclaredField("location").get(defaultOptions); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void anonymous_OnlyOverlay() throws NoSuchFieldException, IllegalAccessException { + Options defaultOptions = new Options() { }; + + Object overlay = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.getClass().getDeclaredField("lastName").get(overlay)); + Assert.assertEquals(result.firstName, overlay.getClass().getDeclaredField("firstName").get(overlay)); + Assert.assertEquals(result.age, overlay.getClass().getDeclaredField("age").get(overlay)); + Assert.assertEquals(result.bool, overlay.getClass().getDeclaredField("bool").get(overlay)); + + Object loc = overlay.getClass().getDeclaredField("location").get(overlay); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void anonymous_FullOverlay() throws NoSuchFieldException, IllegalAccessException { + Object defaultOptions = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Object overlay = new Object() { + public String lastName = "Grant"; + public String firstName = "Eddit"; + public Integer age = 32; + public Boolean bool = false; + public Object location = new Object() { + public Float latitude = 2.2312312F; + public Float longitude = 2.234234F; + }; + }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.getClass().getDeclaredField("lastName").get(overlay)); + Assert.assertEquals(result.firstName, overlay.getClass().getDeclaredField("firstName").get(overlay)); + Assert.assertEquals(result.age, overlay.getClass().getDeclaredField("age").get(overlay)); + Assert.assertEquals(result.bool, overlay.getClass().getDeclaredField("bool").get(overlay)); + + Object loc = overlay.getClass().getDeclaredField("location").get(overlay); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void anonymous_PartialOverlay() throws NoSuchFieldException, IllegalAccessException { + Object defaultOptions = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Object overlay = new Object() { + public String lastName = "Grant"; + }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.getClass().getDeclaredField("lastName").get(overlay)); + Assert.assertEquals(result.firstName, defaultOptions.getClass().getDeclaredField("firstName").get(defaultOptions)); + Assert.assertEquals(result.age, defaultOptions.getClass().getDeclaredField("age").get(defaultOptions)); + Assert.assertEquals(result.bool, defaultOptions.getClass().getDeclaredField("bool").get(defaultOptions)); + + Object loc = defaultOptions.getClass().getDeclaredField("location").get(defaultOptions); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void jsonNode_OnlyDefaultTest() { + JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + JsonNode overlay = Serialization.objectToTree(new Options()); + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.get("lastName").asText()); + Assert.assertEquals(result.firstName, defaultOptions.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), defaultOptions.get("age").asInt()); + Assert.assertEquals(result.bool, defaultOptions.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, defaultOptions.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, defaultOptions.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void jsonNode_OnlyOverlay() { + JsonNode defaultOptions = Serialization.objectToTree(new Options()); + + JsonNode overlay = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.get("lastName").asText()); + Assert.assertEquals(result.firstName, overlay.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), overlay.get("age").asInt()); + Assert.assertEquals(result.bool, overlay.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, overlay.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, overlay.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void jsonNode_FullOverlay() { + JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + JsonNode overlay = Serialization.objectToTree(new Options() {{ + lastName = "Grant"; + firstName = "Eddit"; + age = 32; + bool = false; + location = new Location() {{ + latitude = 2.2312312F; + longitude = 2.234234F; + }}; + }}); + + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.get("lastName").asText()); + Assert.assertEquals(result.firstName, overlay.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), overlay.get("age").asInt()); + Assert.assertEquals(result.bool, overlay.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, overlay.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, overlay.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void jsonNode_PartialOverlay() { + JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + JsonNode overlay = Serialization.objectToTree(new Options() {{ + lastName = "Grant"; + }}); + + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.get("lastName").asText()); + Assert.assertEquals(result.firstName, defaultOptions.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), defaultOptions.get("age").asInt()); + Assert.assertEquals(result.bool, defaultOptions.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, defaultOptions.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, defaultOptions.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void nullStartObject() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options result = ObjectPath.merge(null, defaultOptions, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void nullOverlay() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options result = ObjectPath.merge(defaultOptions, null, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void tryGetPathValue() throws IOException { + PathTest test = new PathTest(); + test.test = "test"; + + test.options = new Options(); + test.options.age = 22; + test.options.firstName = "joe"; + test.options.lastName = "blow"; + test.options.bool = true; + + test.bar = new Bar(); + test.bar.numIndex = 2; + test.bar.strIndex = "FirstName"; + test.bar.objIndex = "options"; + test.bar.options = new Options(); + test.bar.options.age = 1; + test.bar.options.firstName = "joe"; + test.bar.options.lastName = "blow"; + test.bar.options.bool = false; + test.bar.numbers = new int[] { 1, 2, 3, 4, 5 }; + + // test with pojo + { + Assert.assertEquals(test, ObjectPath.getPathValue(test, "", PathTest.class)); + Assert.assertEquals(test.test, ObjectPath.getPathValue(test, "test", String.class)); + Assert.assertEquals( + test.bar.options.age, + ObjectPath.getPathValue(test, "bar.options.age", Integer.class) + ); + + Options options = ObjectPath.tryGetPathValue(test, "options", Options.class); + Assert.assertNotNull(options); + Assert.assertEquals(test.options.age, options.age); + Assert.assertEquals(test.options.firstName, options.firstName); + + Options barOptions = ObjectPath.tryGetPathValue(test, "bar.options", Options.class); + Assert.assertNotNull(barOptions); + Assert.assertEquals(test.bar.options.age, barOptions.age); + Assert.assertEquals(test.bar.options.firstName, barOptions.firstName); + + int[] numbers = ObjectPath.tryGetPathValue(test, "bar.numbers", int[].class); + Assert.assertEquals(5, numbers.length); + + int number = ObjectPath.tryGetPathValue(test, "bar.numbers[1]", Integer.class); + Assert.assertEquals(2, number); + + number = ObjectPath.tryGetPathValue(test, "bar['options'].Age", Integer.class); + Assert.assertEquals(1, number); + + number = ObjectPath.tryGetPathValue(test, "bar[\"options\"].Age", Integer.class); + Assert.assertEquals(1, number); + + number = ObjectPath.tryGetPathValue(test, "bar.numbers[bar.numIndex]", Integer.class); + Assert.assertEquals(3, number); + + number = ObjectPath + .tryGetPathValue(test, "bar.numbers[bar[bar.objIndex].Age]", Integer.class); + Assert.assertEquals(2, number); + + String name = ObjectPath + .tryGetPathValue(test, "bar.options[bar.strIndex]", String.class); + Assert.assertEquals("joe", name); + + int age = ObjectPath.tryGetPathValue(test, "bar[bar.objIndex].Age", Integer.class); + Assert.assertEquals(1, age); + } + + // test with json + { + String json = Serialization.toString(test); + JsonNode jtest = Serialization.jsonToTree(json); + + Assert.assertEquals(jtest, ObjectPath.getPathValue(test, "", JsonNode.class)); + } + } + + // Test classes + // + // Note: This is different from the support dotnet provides due to Java + // not having the same notion of dynamic and anonymous types. Jackson + // itself does not support anonymous inner class deserialization. So our + // support only extends to POJO's which conform to a Bean (public + // members or accessors). + public static class PathTest { + public String test; + public Options options; + public Bar bar; + } + + public static class Location { + public Float latitude; + public Float longitude; + } + + public static class Options { + public String firstName; + public String lastName; + public Integer age; + public Boolean bool; + public Location location; + } + + public static class Bar { + public Integer numIndex; + public String strIndex; + public String objIndex; + public Options options; + public int[] numbers; + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java new file mode 100644 index 000000000..8361acb70 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.microsoft.bot.dialogs.prompts.PromptCultureModel; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; + +import org.javatuples.Pair; +import org.junit.Assert; +import org.junit.Test; + + + +public class PromptCultureModelTests { + + @Test + public void MapCulturesToNearest() { + + List<Pair<String, String>> testData = new ArrayList<Pair<String, String>>(); + testData.add(Pair.with("en-us", "en-us")); + testData.add(Pair.with("en-US", "en-us")); + testData.add(Pair.with("en-Us", "en-us")); + testData.add(Pair.with("EN", "en-us")); + testData.add(Pair.with("en", "en-us")); + testData.add(Pair.with("es-es", "es-es")); + testData.add(Pair.with("es-ES", "es-es")); + testData.add(Pair.with("es-Es", "es-es")); + testData.add(Pair.with("ES", "es-es")); + testData.add(Pair.with("es", "es-es")); + + for (Pair<String, String> pair : testData) { + ShouldCorrectlyMapToNearesLanguage(pair.getValue0(), pair.getValue1()); + } + + } + + public void ShouldCorrectlyMapToNearesLanguage(String localeVariation, String expectedResult) { + String result = PromptCultureModels.mapToNearestLanguage(localeVariation); + Assert.assertEquals(expectedResult, result); + } + + @Test + public void ShouldReturnAllSupportedCultures() { + PromptCultureModel[] expected = new PromptCultureModel[] { + // Bulgarian, + // Chinese, + // Dutch, + PromptCultureModels.ENGLISH, + // French, + // German, + // Hindi, + // Italian, + // Japanese, + // Korean, + // Portuguese, + PromptCultureModels.SPANISH + // Swedish, + // Turkish + }; + + PromptCultureModel[] supportedCultures = PromptCultureModels.getSupportedCultures(); + + List<PromptCultureModel> expectedList = Arrays.asList(expected); + List<PromptCultureModel> supportedList = Arrays.asList(supportedCultures); + for (PromptCultureModel promptCultureModel : expectedList) { + Assert.assertTrue(supportedList.contains(promptCultureModel)); + } + + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java new file mode 100644 index 000000000..ef71e46aa --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidator; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.junit.Test; + +public class PromptValidatorContextTests { + + @Test + public void PromptValidatorContextEnd() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<String> validator = (promptContext) -> { + return CompletableFuture.completedFuture(true); + }; + + dialogs.add(new TextPrompt("namePrompt", validator)); + + WaterfallStep[] steps = new WaterfallStep[] + { + new WaterfallStep1(), + new WaterfallStep2() + }; + + dialogs.add(new WaterfallDialog("nameDialog", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("nameDialog").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please type your name.") + .send("John") + .assertReply("John is a great name!") + .send("Hi again") + .assertReply("Please type your name.") + .send("1") + .assertReply("1 is a great name!") + .startTest() + .join(); + } + + private class WaterfallStep1 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions prompt = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Please type your name."); + prompt.setPrompt(activity); + return stepContext.prompt("namePrompt", prompt); + } + + } + + private class WaterfallStep2 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + String name = (String)stepContext.getResult(); + stepContext.getContext().sendActivity( + MessageFactory.text(String.format("%s is a great name!", name))).join(); + return stepContext.endDialog(); + } + } + + @Test + public void PromptValidatorContextRetryEnd() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<String> validator = (promptContext) -> { + String result = promptContext.getRecognized().getValue(); + if (result.length() > 3) { + return CompletableFuture.completedFuture(true); + } else { + promptContext.getContext().sendActivity( + MessageFactory.text("Please send a name that is longer than 3 characters.")); + } + + return CompletableFuture.completedFuture(false); + }; + + dialogs.add(new TextPrompt("namePrompt", validator)); + + + // Create TextPrompt with dialogId "namePrompt" and custom validator + WaterfallStep[] steps = new WaterfallStep[] + { + new WaterfallStep1(), + new WaterfallStep2() + }; + + dialogs.add(new WaterfallDialog("nameDialog", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("nameDialog").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please type your name.") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters.") + .send("John") + .assertReply("John is a great name!") + .startTest() + .join(); + } + + @Test + public void PromptValidatorNumberOfAttempts() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<String> validator = (promptContext) -> { + String result = promptContext.getRecognized().getValue(); + if (result.length() > 3) { + Activity succeededMessage = MessageFactory.text(String.format("You got it at the %nth try!", + promptContext.getAttemptCount())); + promptContext.getContext().sendActivity(succeededMessage); + return CompletableFuture.completedFuture(true); + } else { + promptContext.getContext().sendActivity( + MessageFactory.text("Please send a name that is longer than 3 characters.")); + } + + return CompletableFuture.completedFuture(false); + }; + + dialogs.add(new TextPrompt("namePrompt", validator)); + + WaterfallStep[] steps = new WaterfallStep[] + { + new WaterfallStep1(), + new WaterfallStep2() + }; + + dialogs.add(new WaterfallDialog("nameDialog", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("nameDialog").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please type your name.") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters. 1") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters. 2") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters. 3") + .send("John") + .assertReply("You got it at the 4th try!") + .assertReply("John is a great name!") + .startTest(); + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java new file mode 100644 index 000000000..80553d51e --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; + +import org.junit.Assert; +import org.junit.Test; + +public class ReplaceDialogTests { + + @Test + public void ReplaceDialogNoBranch() { + FirstDialog dialog = new FirstDialog(); + + MemoryStorage storage = new MemoryStorage(); + UserState userState = new UserState(storage); + ConversationState conversationState = new ConversationState(storage); + TestAdapter adapter = new TestAdapter() + .useStorage(storage) + .useBotState(userState, conversationState); + DialogManager dialogManager = new DialogManager(dialog, null); + new TestFlow(adapter, (turnContext) -> { + dialogManager.onTurn(turnContext).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("prompt one") + .send("hello") + .assertReply("prompt two") + .send("hello") + .assertReply("prompt three") + .send("hello") + .assertReply("*** WaterfallDialog End ***") + .startTest() + .join(); + } + + @Test + public void ReplaceDialogBranch() { + FirstDialog dialog = new FirstDialog(); + + MemoryStorage storage = new MemoryStorage(); + UserState userState = new UserState(storage); + ConversationState conversationState = new ConversationState(storage); + TestAdapter adapter = new TestAdapter() + .useStorage(storage) + .useBotState(userState, conversationState); + DialogManager dialogManager = new DialogManager(dialog, null); + new TestFlow(adapter, (turnContext) -> { + dialogManager.onTurn(turnContext).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("prompt one") + .send("hello") + .assertReply("prompt two") + .send("replace") + .assertReply("*** WaterfallDialog End ***") + .assertReply("prompt four") + .send("hello") + .assertReply("prompt five") + .send("hello") + .startTest() + .join(); + } + + @Test + public void ReplaceDialogTelemetryClientNotNull() { + FirstDialog dialog = new FirstDialog(); + + MemoryStorage storage = new MemoryStorage(); + UserState userState = new UserState(storage); + ConversationState conversationState = new ConversationState(storage); + TestAdapter adapter = new TestAdapter() + .useStorage(storage) + .useBotState(userState, conversationState); + DialogManager dialogManager = new DialogManager(dialog, null); + new TestFlow(adapter, (turnContext) -> { + Assert.assertNotNull(dialog.getTelemetryClient()); + dialogManager.onTurn(turnContext).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .startTest(); + } + + private class FirstDialog extends ComponentDialog { + public FirstDialog() { + super("FirstDialog"); + WaterfallStep[] steps = new WaterfallStep[] { + new ActionOne(), + new ActionTwo(), + new ReplaceAction(), + new ActionThree(), + new LastAction(), + }; + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new SecondDialog()); + addDialog(new WaterfallWithEndDialog("WaterfallWithEndDialog", steps)); + + setInitialDialogId("WaterfallWithEndDialog"); + } + + private class ActionOne implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt one")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class ActionTwo implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt two")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class ReplaceAction implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if ((String) stepContext.getResult() == "replace") { + return stepContext.replaceDialog("SecondDialog"); + } else { + return stepContext.next(null); + } + } + } + private class ActionThree implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt three")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class LastAction implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + return stepContext.endDialog(); + } + } + + private class WaterfallWithEndDialog extends WaterfallDialog { + private WaterfallWithEndDialog(String id, WaterfallStep[] steps) { + super(id, Arrays.asList(steps)); + } + + @Override + public CompletableFuture<Void> endDialog(TurnContext turnContext, DialogInstance instance, + DialogReason reason) { + turnContext.sendActivity(MessageFactory.text("*** WaterfallDialog End ***")).join(); + return super.endDialog(turnContext, instance, reason); + } + } + } + + private class SecondDialog extends ComponentDialog { + private SecondDialog() { + super("SecondDialog"); + WaterfallStep[] steps = new WaterfallStep[] { + new ActionFour(), + new ActionFive(), + new LastAction(), + }; + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(steps))); + + setInitialDialogId("WaterfallDialog"); + } + + private class ActionFour implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt four")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class ActionFive implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt five")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class LastAction implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + return stepContext.endDialog(); + } + + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java new file mode 100644 index 000000000..9d2f05703 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.microsoft.bot.dialogs.prompts.PromptCultureModel; + +public class TestLocale { + + private String validLocale; + + private String capEnding; + + private String titleEnding; + + private String capTwoLetter; + + private String lowerTwoLetter; + + private String expectedPrompt; + + private String inputThatResultsInOne; + + private String inputThatResultsInZero; + + private PromptCultureModel culture; + + public TestLocale( + PromptCultureModel cultureModel, + String expectedPrompt, + String inputThatResultsInOne, + String inputThatResultsInZero) { + if (cultureModel.getLocale().length() != 5) { + throw new IllegalArgumentException("validLocale must be in format: es-es"); + } + + this.culture = cultureModel; + this.validLocale = cultureModel.getLocale().toString().toLowerCase(); + this.expectedPrompt = expectedPrompt; + this.inputThatResultsInOne = inputThatResultsInOne; + this.inputThatResultsInZero = inputThatResultsInZero; + + // es-ES + capEnding = getCapEnding(validLocale); + + // es-Es + titleEnding = getTitleEnding(validLocale); + + // ES + capTwoLetter = getCapTwoLetter(validLocale); + + // es + lowerTwoLetter = getLowerTwoLetter(validLocale); + } + + public String getSeparator() { + if (culture != null) { + return culture.getSeparator(); + } else { + return ""; + } + } + + public String getInlineOr() { + if (culture != null) { + return culture.getInlineOr(); + } else { + return ""; + } + } + + public String getInlineOrMore() { + if (culture != null) { + return culture.getInlineOrMore(); + } else { + return ""; + } + } + + + private String getCapEnding(String locale) { + return String.format("%s%s-%s%s", + locale.substring(0, 1), + locale.substring(1, 2), + locale.substring(3, 4).toUpperCase(), + locale.substring(4, 5).toUpperCase()); + } + + private String getTitleEnding(String locale) { + return String.format("%s%s-%s%s", + locale.substring(0, 1), + locale.substring(1, 2), + locale.substring(3, 4).toUpperCase(), + locale.substring(4, 5)); + } + + private String getCapTwoLetter(String locale) { + return String.format("%s%s", + locale.substring(0, 1).toUpperCase(), + locale.substring(1, 2).toUpperCase()); + } + + private String getLowerTwoLetter(String locale) { + return String.format("%s%s", + locale.substring(0, 1), + locale.substring(1, 2)); + } + + /** + * @return the ValidLocale value as a String. + */ + public String getValidLocale() { + return this.validLocale; + } + + /** + * @return the CapEnding value as a String. + */ + public String getCapEnding() { + return this.capEnding; + } + + /** + * @return the TitleEnding value as a String. + */ + public String getTitleEnding() { + return this.titleEnding; + } + + /** + * @return the CapTwoLetter value as a String. + */ + public String getCapTwoLetter() { + return this.capTwoLetter; + } + + /** + * @return the LowerTwoLetter value as a String. + */ + public String getLowerTwoLetter() { + return this.lowerTwoLetter; + } + + /** + * @return the ExpectedPrompt value as a String. + */ + public String getExpectedPrompt() { + return this.expectedPrompt; + } + + /** + * @return the InputThatResultsInOne value as a String. + */ + public String getInputThatResultsInOne() { + return this.inputThatResultsInOne; + } + + /** + * @return the InputThatResultsInZero value as a String. + */ + public String getInputThatResultsInZero() { + return this.inputThatResultsInZero; + } + + /** + * @return the Culture value as a PromptCultureModel. + */ + public PromptCultureModel getCulture() { + return this.culture; + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java new file mode 100644 index 000000000..188f1a2cb --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java @@ -0,0 +1,545 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.DateTimePrompt; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.NotImplementedException; +import org.junit.Test; +import org.junit.Assert; + +public class WaterfallTests { + + public WaterfallDialog Create_Waterfall3() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall3_Step1(), + new Waterfall3_Step2() + }; + return new WaterfallDialog("test-waterfall-a", Arrays.asList(steps)); + } + + public WaterfallDialog Create_Waterfall4() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall4_Step1(), + new Waterfall4_Step2() + }; + return new WaterfallDialog("test-waterfall-b", Arrays.asList(steps)); + } + + public WaterfallDialog Create_Waterfall5() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall5_Step1(), + new Waterfall5_Step2() + }; + return new WaterfallDialog("test-waterfall-c", Arrays.asList(steps)); + } + + @Test + public void Waterfall() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + WaterfallStep[] steps = new WaterfallStep[] + {(step) -> { + step.getContext().sendActivity("step1"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step2"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step3"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, + }; + dialogs.add(new WaterfallDialog("test", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallStepParentIsWaterfallParent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + final String WATERFALL_PARENT_D = "waterfall-parent-test-dialog"; + ComponentDialog waterfallParent = new ComponentDialog(WATERFALL_PARENT_D); + + WaterfallStep[] steps = new WaterfallStep[] {(step) -> { + Assert.assertEquals(step.getParent().getActiveDialog().getId(), waterfallParent.getId()); + step.getContext().sendActivity("verified").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } }; + + waterfallParent.addDialog(new WaterfallDialog("test", Arrays.asList(steps))); + waterfallParent.setInitialDialogId("test"); + dialogs.add(waterfallParent); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog(WATERFALL_PARENT_D, null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("verified") + .startTest() + .join(); + } + + @Test + public void WaterfallWithCallback() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + WaterfallStep[] steps = new WaterfallStep[] {(step) -> { + step.getContext().sendActivity("step1"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step2"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step3"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, }; + WaterfallDialog waterfallDialog = new WaterfallDialog("test", Arrays.asList(steps)); + + dialogs.add(waterfallDialog); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallWithStepsNull() { + Assert.assertThrows(IllegalArgumentException.class, () -> new WaterfallDialog("test", null).addStep(null)); + } + + @Test + public void WaterfallWithClass() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + + dialogs.add(new MyWaterfallDialog("test")); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallPrompt() throws UnsupportedDataTypeException{ + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(Create_Waterfall2()); + NumberPrompt<Integer> numberPrompt = null; + try { + numberPrompt = new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, + Integer.class); + } catch (UnsupportedDataTypeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + dialogs.add(numberPrompt); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + dc.continueDialog().join(); + + if (!turnContext.getResponded()) { + dc.beginDialog("test-waterfall").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .assertReply("Enter a number.") + .send("hello again") + .assertReply("It must be a number") + .send("42") + .assertReply("Thanks for '42'") + .assertReply("step2") + .assertReply("Enter a number.") + .send("apple") + .assertReply("It must be a number") + .send("orange") + .assertReply("It must be a number") + .send("64") + .assertReply("Thanks for '64'") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallNested() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + new TestFlow(adapter, (turnContext) -> { + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(Create_Waterfall3()); + dialogs.add(Create_Waterfall4()); + dialogs.add(Create_Waterfall5()); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + dc.continueDialog().join(); + + if (!turnContext.getResponded()) { + dc.beginDialog("test-waterfall-a").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .assertReply("step1.1") + .send("hello") + .assertReply("step1.2") + .send("hello") + .assertReply("step2") + .assertReply("step2.1") + .send("hello") + .assertReply("step2.2") + .startTest(); + } + + @Test + public void WaterfallDateTimePromptFirstInvalidThenValidInput() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + DialogSet dialogs = new DialogSet(dialogState); + + dialogs.add(new DateTimePrompt("dateTimePrompt", null, PromptCultureModels.ENGLISH_CULTURE)); + + WaterfallStep[] steps = new WaterfallStep[] { + (stepContext) -> { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Provide a date"); + return stepContext.prompt("dateTimePrompt", options); + }, + (stepContext) -> { + Assert.assertNotNull(stepContext); + return stepContext.endDialog(); + }, + }; + + dialogs.add(new WaterfallDialog("test-dateTimePrompt", Arrays.asList(steps))); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + dc.continueDialog().join(); + + if (!turnContext.getResponded()) { + dc.beginDialog("test-dateTimePrompt", null); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Provide a date") + .send("hello again") + .assertReply("Provide a date") + .send("Wednesday 4 oclock") + .startTest(); + } + + @Test + public void WaterfallCancel() { + final String id = "waterfall"; + final int index = 1; + + MyWaterfallDialog dialog = new MyWaterfallDialog(id); + MyBotTelemetryClient telemetryClient = new MyBotTelemetryClient("Waterfall2_Step2"); + dialog.setTelemetryClient(telemetryClient); + + DialogInstance dialogInstance = new DialogInstance(); + dialogInstance.setId(id); + Map<String, Object> stateMap = new HashMap<String, Object>(); + stateMap.put("stepIndex", index); + stateMap.put("instanceId", "(guid)"); + dialogInstance.setState(stateMap); + + dialog.endDialog( + new TurnContextImpl(new TestAdapter(), new Activity(ActivityTypes.MESSAGE)), + dialogInstance, + DialogReason.CANCEL_CALLED); + + Assert.assertTrue(telemetryClient.trackEventCallCount > 0); + } + + private WaterfallDialog Create_Waterfall2() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall2_Step1(), + new Waterfall2_Step2(), + new Waterfall2_Step3() + }; + return new WaterfallDialog("test-waterfall", Arrays.asList(steps)); + } + + private class Waterfall2_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step1"); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + options.setRetryPrompt(MessageFactory.text("It must be a number")); + return stepContext.prompt("number", options); + } + } + + private class Waterfall2_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)); + } + + stepContext.getContext().sendActivity("step2"); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + options.setRetryPrompt(MessageFactory.text("It must be a number")); + return stepContext.prompt("number", options); + } + } + + private class Waterfall2_Step3 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)); + } + + stepContext.getContext().sendActivity("step3"); + Map<String, Object> resultMap = new HashMap<String, Object>(); + resultMap.put("Value", "All Done!"); + return stepContext.endDialog(resultMap); + } + } + private class Waterfall3_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step1")); + return stepContext.beginDialog("test-waterfall-b", null); + } + } + + private class Waterfall3_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step2")); + return stepContext.beginDialog("test-waterfall-c", null); + } + } + + private class Waterfall4_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step1.1")); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall4_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step1.2")); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall5_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step2.1")); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall5_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step2.2")); + return stepContext.endDialog(); + } + } + + private final class MyBotTelemetryClient implements BotTelemetryClient { + + private int trackEventCallCount = 0; + private String stepNameToMatch = ""; + + private MyBotTelemetryClient(String stepNameToMatch) { + this.stepNameToMatch = stepNameToMatch; + } + + @Override + public void trackAvailability(String name, OffsetDateTime timeStamp, Duration duration, String runLocation, + boolean success, String message, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackAvailability is not implemented"); + } + + @Override + public void trackDependency(String dependencyTypeName, String target, String dependencyName, String data, + OffsetDateTime startTime, Duration duration, String resultCode, boolean success) { + throw new NotImplementedException("trackDependency is not implemented"); + } + + @Override + public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) { + String stepName = properties.get("StepName"); + if (stepName != null && stepName.equals(stepNameToMatch)) { + trackEventCallCount++; + } + } + + @Override + public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackException is not implemented"); + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) { + throw new NotImplementedException("trackTrace is not implemented"); + } + + @Override + public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackDialogView is not implemented"); + } + + @Override + public void flush() { + throw new NotImplementedException("flush is not implemented"); + } + } + + private class MyWaterfallDialog extends WaterfallDialog { + public MyWaterfallDialog(String id) { + super(id, null); + addStep(new Waterfall2_Step1()); + addStep(new Waterfall2_Step2()); + addStep(new Waterfall2_Step3()); + } + + private class Waterfall2_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step1").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall2_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step2").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + private class Waterfall2_Step3 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step3").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java new file mode 100644 index 000000000..03cb1e993 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ChoiceFactoryTests { + private static List<Choice> colorChoices = Arrays.asList(new Choice("red"), new Choice("green"), new Choice("blue")); + private static List<Choice> extraChoices = Arrays.asList(new Choice("red"), new Choice("green"), new Choice("blue"), new Choice("alpha")); + private static List<Choice> choicesWithActions = Arrays.asList( + new Choice("ImBack") {{ + setAction(new CardAction() {{ + setType(ActionTypes.IM_BACK); + setTitle("ImBack Action"); + setValue("ImBack Value"); + }}); + }}, + new Choice("MessageBack") {{ + setAction(new CardAction() {{ + setType(ActionTypes.MESSAGE_BACK); + setTitle("MessageBack Action"); + setValue("MessageBack Value"); + }}); + }}, + new Choice("PostBack") {{ + setAction(new CardAction() {{ + setType(ActionTypes.POST_BACK); + setTitle("PostBack Action"); + setValue("PostBack Value"); + }}); + }} + ); + + @Test + public void shouldRenderChoicesInline() { + Activity activity = ChoiceFactory.inline(colorChoices, "select from:"); + Assert.assertEquals("select from: (1) red, (2) green, or (3) blue", activity.getText()); + } + + @Test + public void shouldRenderChoicesAsAList() { + Activity activity = ChoiceFactory.list(colorChoices, "select from:"); + Assert.assertEquals("select from:\n\n 1. red\n 2. green\n 3. blue", activity.getText()); + } + + @Test + public void shouldRenderUnincludedNumbersChoicesAsAList() { + Activity activity = ChoiceFactory.list(colorChoices, "select from:", null, new ChoiceFactoryOptions() {{ setIncludeNumbers(false); }}); + Assert.assertEquals("select from:\n\n - red\n - green\n - blue", activity.getText()); + } + + @Test + public void shouldRenderChoicesAsSuggestedActions() { + Activity activity = ChoiceFactory.suggestedAction(colorChoices, "select from:"); + Assert.assertEquals("select from:", activity.getText()); + Assert.assertNotNull(activity.getSuggestedActions()); + Assert.assertEquals(3, activity.getSuggestedActions().getActions().size()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(0).getType()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getValue()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(1).getType()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getValue()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(2).getType()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getValue()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getTitle()); + } + + @Test + public void shouldRenderChoicesAsHeroCard() { + Activity activity = ChoiceFactory.heroCard(colorChoices, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getTitle()); + } + + @Test + public void shouldAutomaticallyChooseRenderStyleBasedOnChannelType() { + Activity activity = ChoiceFactory.forChannel(Channels.EMULATOR, colorChoices, "select from:"); + Assert.assertEquals("select from:", activity.getText()); + Assert.assertNotNull(activity.getSuggestedActions()); + Assert.assertEquals(3, activity.getSuggestedActions().getActions().size()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(0).getType()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getValue()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(1).getType()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getValue()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(2).getType()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getValue()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getTitle()); + } + + @Test + public void shouldChooseCorrectStylesForCortana() { + Activity activity = ChoiceFactory.forChannel(Channels.CORTANA, colorChoices, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getTitle()); + } + + @Test + public void shouldChooseCorrectStylesForTeams() { + Activity activity = ChoiceFactory.forChannel(Channels.MSTEAMS, colorChoices, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getTitle()); + } + + @Test + public void shouldIncludeChoiceActionsInSuggestedActions() { + Activity activity = ChoiceFactory.suggestedAction(choicesWithActions, "select from:"); + Assert.assertEquals("select from:", activity.getText()); + Assert.assertNotNull(activity.getSuggestedActions()); + Assert.assertEquals(3, activity.getSuggestedActions().getActions().size()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(0).getType()); + Assert.assertEquals("ImBack Value", activity.getSuggestedActions().getActions().get(0).getValue()); + Assert.assertEquals("ImBack Action", activity.getSuggestedActions().getActions().get(0).getTitle()); + Assert.assertEquals(ActionTypes.MESSAGE_BACK, activity.getSuggestedActions().getActions().get(1).getType()); + Assert.assertEquals("MessageBack Value", activity.getSuggestedActions().getActions().get(1).getValue()); + Assert.assertEquals("MessageBack Action", activity.getSuggestedActions().getActions().get(1).getTitle()); + Assert.assertEquals(ActionTypes.POST_BACK, activity.getSuggestedActions().getActions().get(2).getType()); + Assert.assertEquals("PostBack Value", activity.getSuggestedActions().getActions().get(2).getValue()); + Assert.assertEquals("PostBack Action", activity.getSuggestedActions().getActions().get(2).getTitle()); + } + + @Test + public void shouldIncludeChoiceActionsInHeroCards() { + Activity activity = ChoiceFactory.heroCard(choicesWithActions, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("ImBack Value", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("ImBack Action", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.MESSAGE_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("MessageBack Value", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("MessageBack Action", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.POST_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("PostBack Value", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("PostBack Action", heroCard.getButtons().get(2).getTitle()); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java new file mode 100644 index 000000000..82ac40997 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.builder.BotFrameworkAdapter; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; +import com.microsoft.bot.schema.Activity; +import org.junit.Assert; +import org.junit.Test; + +public class ChoicesChannelTests { + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithLineAnd13() { + boolean supports = Channel.supportsSuggestedActions(Channels.LINE, 13); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithLineAnd14() { + boolean supports = Channel.supportsSuggestedActions(Channels.LINE, 14); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithSkypeAnd10() { + boolean supports = Channel.supportsSuggestedActions(Channels.SKYPE, 10); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithSkypeAnd11() { + boolean supports = Channel.supportsSuggestedActions(Channels.SKYPE, 11); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithKikAnd20() { + boolean supports = Channel.supportsSuggestedActions(Channels.KIK, 20); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithKikAnd21() { + boolean supports = Channel.supportsSuggestedActions(Channels.SKYPE, 21); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithEmulatorAnd100() { + boolean supports = Channel.supportsSuggestedActions(Channels.EMULATOR, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithEmulatorAnd101() { + boolean supports = Channel.supportsSuggestedActions(Channels.EMULATOR, 101); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithDirectLineSpeechAnd100() { + boolean supports = Channel.supportsSuggestedActions(Channels.DIRECTLINESPEECH, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithDirectLineSpeechAnd99() { + boolean supports = Channel.supportsCardActions(Channels.DIRECTLINESPEECH, 99); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithLineAnd99() { + boolean supports = Channel.supportsCardActions(Channels.LINE, 99); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsCardActionsWithLineAnd100() { + boolean supports = Channel.supportsCardActions(Channels.LINE, 100); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithCortanaAnd100() { + boolean supports = Channel.supportsCardActions(Channels.CORTANA, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithSlackAnd100() { + boolean supports = Channel.supportsCardActions(Channels.SLACK, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithSkypeAnd100() { + boolean supports = Channel.supportsCardActions(Channels.SKYPE, 3); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsCardActionsWithSkypeAnd5() { + boolean supports = Channel.supportsCardActions(Channels.SKYPE, 5); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnFalseForHasMessageFeedWithCortana() { + boolean supports = Channel.hasMessageFeed(Channels.CORTANA); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnChannelIdFromContextActivity() { + Activity testActivity = new Activity() {{ setChannelId(Channels.FACEBOOK); }}; + TurnContext testContext = new TurnContextImpl(new BotFrameworkAdapter(new SimpleCredentialProvider()), testActivity); + String channelId = Channel.getChannelId(testContext); + Assert.assertEquals(Channels.FACEBOOK, channelId); + } + + @Test + public void shouldReturnEmptyFromContextActivityMissingChannel() { + Activity testActivity = new Activity() {{ setChannelId(null); }}; + TurnContext testContext = new TurnContextImpl(new BotFrameworkAdapter(new SimpleCredentialProvider()), testActivity); + String channelId = Channel.getChannelId(testContext); + Assert.assertNull(channelId); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java new file mode 100644 index 000000000..a55b4d31f --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ChoicesRecognizerTests { + + private static List<String> colorChoices = Arrays.asList("red", "green", "blue"); + private static List<String> overlappingChoices = Arrays + .asList("bread", "bread pudding", "pudding"); + + private static List<SortedValue> colorValues = Arrays.asList( + new SortedValue("red", 0), + new SortedValue("green", 1), + new SortedValue("blue", 2) + ); + + private static List<SortedValue> overlappingValues = Arrays.asList( + new SortedValue("bread", 0), + new SortedValue("bread pudding", 1), + new SortedValue("pudding", 2) + ); + + private static List<SortedValue> similarValues = Arrays.asList( + new SortedValue("option A", 0), + new SortedValue("option B", 1), + new SortedValue("option C", 2) + ); + + // + // FindChoices + // + + @Test + public void shouldFindASimpleValueInAnSingleWordUtterance() { + List<ModelResult<FoundValue>> found = Find.findValues("red", colorValues); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 0, 2, "red"); + assertValue(found.get(0), "red", 0, 1.0f); + } + + @Test + public void shouldFindASimpleValueInAnUtterance() { + List<ModelResult<FoundValue>> found = Find.findValues("the red one please.", colorValues); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertValue(found.get(0), "red", 0, 1.0f); + } + + @Test + public void shouldFindMultipleValuesWithinAnUtterance() { + List<ModelResult<FoundValue>> found = Find.findValues("the red and blue ones please.", colorValues); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertValue(found.get(0), "red", 0, 1.0f); + assertValue(found.get(1), "blue", 2, 1.0f); + } + + @Test + public void shouldFindMultipleValuesThatOverlap() { + List<ModelResult<FoundValue>> found = Find.findValues("the bread pudding and bread please.", overlappingValues); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 16, "bread pudding"); + assertValue(found.get(0), "bread pudding", 1, 1.0f); + assertValue(found.get(1), "bread", 0, 1.0f); + } + + @Test + public void shouldCorrectlyDisambiguateBetweenVerySimilarValues() { + List<ModelResult<FoundValue>> found = Find.findValues("option B", similarValues, new FindChoicesOptions() {{ setAllowPartialMatches(true);}}); + Assert.assertEquals(1, found.size()); + assertValue(found.get(0), "option B", 1, 1.0f); + } + + @Test + public void shouldFindASingleChoiceInAnUtterance() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings("the red one please.", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesWithinAnUtterance() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings("the red and blue ones please.", colorChoices); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + assertChoice(found.get(1), "blue", 2, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesThatOverlap() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings("the bread pudding and bread please.", overlappingChoices); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 16, "bread pudding"); + assertChoice(found.get(0), "bread pudding", 1, 1.0f, null); + assertChoice(found.get(1), "bread", 0, 1.0f, null); + } + + @Test + public void shouldAcceptNullUtteranceInFindChoices() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings(null, colorChoices); + Assert.assertEquals(0, found.size()); + } + + // + // RecognizeChoices + // + + @Test + public void shouldFindAChoiceInAnUtteranceByName() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("the red one please", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindAChoiceInAnUtteranceByOrdinalPosition() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("the first one please.", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 8, "first"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesInAnUtteranceByOrdinalPosition() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("the first and third one please.", colorChoices); + Assert.assertEquals(2, found.size()); + assertChoice(found.get(0), "red", 0, 1.0f, null); + assertChoice(found.get(1), "blue", 2, 1.0f, null); + } + + @Test + public void shouldFindAChoiceInAnUtteranceByNumericalIndex_digit() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("1", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 0, 0, "1"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindAChoiceInAnUtteranceByNumericalIndex_Text() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("one", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 0, 2, "one"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesInAnUtteranceByNumerical_index() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("option one and 3.", colorChoices); + Assert.assertEquals(2, found.size()); + assertChoice(found.get(0), "red", 0, 1.0f, null); + assertChoice(found.get(1), "blue", 2, 1.0f, null); + } + + @Test + public void shouldAcceptNullUtteranceInRecognizeChoices() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings(null, colorChoices); + Assert.assertEquals(0, found.size()); + } + + @Test + public void shouldNOTFindAChoiceInAnUtteranceByOrdinalPosition_RecognizeOrdinalsFalseAndRecognizeNumbersFalse() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings( + "the first one please.", + colorChoices, + new FindChoicesOptions() {{ setRecognizeOrdinals(false); setRecognizeNumbers(false);}}); + Assert.assertEquals(0, found.size()); + } + + @Test + public void shouldNOTFindAChoiceInAnUtteranceByNumericalIndex_Text_RecognizeNumbersFalse() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings( + "one", + colorChoices, + new FindChoicesOptions() {{ setRecognizeNumbers(false);}}); + Assert.assertEquals(0, found.size()); + } + + // + // Helper methods + // + + private static <T> void assertResult(ModelResult<T> result, int start, int end, String text) { + Assert.assertEquals(start, result.getStart()); + Assert.assertEquals(end, result.getEnd()); + Assert.assertEquals(text, result.getText()); + } + + private static void assertValue(ModelResult<FoundValue> result, String value, int index, float score) { + Assert.assertEquals("value", result.getTypeName()); + Assert.assertNotNull(result.getResolution()); + FoundValue resolution = result.getResolution(); + Assert.assertEquals(value, resolution.getValue()); + Assert.assertEquals(index, resolution.getIndex()); + Assert.assertEquals(score, resolution.getScore(), .01f); + } + + private static void assertChoice(ModelResult<FoundChoice> result, String value, int index, float score, String synonym) { + Assert.assertEquals("choice", result.getTypeName()); + Assert.assertNotNull(result.getResolution()); + FoundChoice resolution = result.getResolution(); + Assert.assertEquals(value, resolution.getValue()); + Assert.assertEquals(index, resolution.getIndex()); + Assert.assertEquals(score, resolution.getScore(), .01f); + if (synonym != null) { + Assert.assertEquals(synonym, resolution.getSynonym()); + } + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java new file mode 100644 index 000000000..57f72cecc --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ChoicesTokenizerTests { + @Test + public void shouldBreakOnSpaces() { + List<Token> tokens = new Tokenizer().tokenize("how now brown cow", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 2, "how"); + assertToken(tokens.get(1), 4, 6, "now"); + assertToken(tokens.get(2), 8, 12, "brown"); + assertToken(tokens.get(3), 14, 16, "cow"); + } + + @Test + public void shouldBreakOnPunctuation() { + List<Token> tokens = new Tokenizer().tokenize("how-now.brown:cow ?", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 2, "how"); + assertToken(tokens.get(1), 4, 6, "now"); + assertToken(tokens.get(2), 8, 12, "brown"); + assertToken(tokens.get(3), 14, 16, "cow"); + } + + @Test + public void shouldTokenizeSingleCharacterTokens() + { + List<Token> tokens = new Tokenizer().tokenize("a b c d", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 0, "a"); + assertToken(tokens.get(1), 2, 2, "b"); + assertToken(tokens.get(2), 4, 4, "c"); + assertToken(tokens.get(3), 6, 6, "d"); + } + + @Test + public void shouldReturnASingleToken() { + List<Token> tokens = new Tokenizer().tokenize("food", null); + Assert.assertEquals(1, tokens.size()); + assertToken(tokens.get(0), 0, 3, "food"); + } + + @Test + public void shouldReturnNoTokens() { + List<Token> tokens = new Tokenizer().tokenize(".?; -()", null); + Assert.assertEquals(0, tokens.size()); + } + + @Test + public void shouldReturnTheNormalizedAndOriginalTextForAToken() { + List<Token> tokens = new Tokenizer().tokenize("fOoD", null); + Assert.assertEquals(1, tokens.size()); + assertToken(tokens.get(0), 0, 3, "fOoD", "food"); + } + + @Test + public void shouldBreakOnEmojis() { + List<Token> tokens = new Tokenizer().tokenize("food \uD83D\uDCA5\uD83D\uDC4D\uD83D\uDE00", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 3, "food"); + assertToken(tokens.get(1), 5, 6, "\uD83D\uDCA5"); + assertToken(tokens.get(2), 7, 8, "\uD83D\uDC4D"); + assertToken(tokens.get(3), 9, 10, "\uD83D\uDE00"); + } + + private static void assertToken(Token token, int start, int end, String text) { + assertToken(token, start, end, text, null); + } + + private static void assertToken(Token token, int start, int end, String text, String normalized) { + Assert.assertEquals(start, token.getStart()); + Assert.assertEquals(end, token.getEnd()); + Assert.assertEquals(text, token.getText()); + Assert.assertEquals(normalized == null ? text : normalized, token.getNormalized()); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java new file mode 100644 index 000000000..160d798c9 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.junit.Assert; +import org.junit.Test; + +public class ActivityPromptTests { + + public ActivityPromptTests(){ + + } + + @Test + public void ActivityPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new EventActivityPrompt("", + new BasicActivityPromptValidator())); + } + + @Test + public void ActivityPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new EventActivityPrompt(null, + new BasicActivityPromptValidator())); + } + + @Test + public void ActivityPromptWithNullValidatorShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new EventActivityPrompt("EventActivityPrompt", null)); + } + + @Test + public void BasicActivityPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add custom activity prompt to DialogSet. + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", + new BasicActivityPromptValidator()); + dialogs.add(eventPrompt); + + // Create mock Activity for testing. + Activity eventActivity = new Activity() { { setType(ActivityTypes.EVENT); setValue(2); }}; + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + options.setPrompt(activity); + dc.prompt("EventActivityPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Activity content = (Activity) results.getResult(); + turnContext.sendActivity(content).join(); + } + return CompletableFuture.completedFuture(null); + }); + + // ActivityPromptHandler handler = new ActivityPromptHandler(); + // handler.setDialogs(dialogs); + + new TestFlow(adapter, botLogic).send("hello").assertReply("please send an event.") + .send(eventActivity).assertReply("2").startTest().join(); + } + + @Test + public void ActivityPromptShouldSendRetryPromptIfValidationFailed() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", new EmptyPromptValidator()); + dialogs.add(eventPrompt); + + Activity eventActivity = new Activity(ActivityTypes.EVENT); + eventActivity.setValue(2); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Retrying - please send an event."); + options.setPrompt(activity); + options.setRetryPrompt(retryActivity); + dc.prompt("EventActivityPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Activity content = (Activity) results.getResult(); + turnContext.sendActivity(content).join(); + } else if (results.getStatus() == DialogTurnStatus.WAITING) { + turnContext.sendActivity("Test complete."); + } + return CompletableFuture.completedFuture(null); + }); + + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please send an event.") + .send("test") + .assertReply("Retrying - please send an event.") + .startTest(); + } + + @Test + public void ActivityPromptResumeDialogShouldPromptNotRetry() + { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", new EmptyPromptValidator()); + dialogs.add(eventPrompt); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + switch (turnContext.getActivity().getText()) { + case "begin": + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Retrying - please send an event."); + options.setPrompt(activity); + options.setRetryPrompt(retryActivity); + dc.prompt("EventActivityPrompt", options).join(); + break; + case "continue": + eventPrompt.continueDialog(dc).join(); + break; + case "resume": + eventPrompt.resumeDialog(dc, DialogReason.NEXT_CALLED).join(); + break; + default: + break; + } + return CompletableFuture.completedFuture(null); + }); + new TestFlow(adapter, botLogic) + .send("begin") + .assertReply("please send an event.") + .send("continue") + .assertReply("Retrying - please send an event.") + .send("resume") + // 'ResumeDialogAsync' of ActivityPrompt does NOT cause a Retry + .assertReply("please send an event.") + .startTest(); + } + + @Test + public void OnPromptOverloadWithoutIsRetryParamReturnsBasicActivityPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityWithoutRetryPrompt", + new BasicActivityPromptValidator()); + dialogs.add(eventPrompt); + + // Create mock Activity for testing. + Activity eventActivity = new Activity(ActivityTypes.EVENT); + eventActivity.setValue(2); + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + options.setPrompt(activity); + dc.prompt("EventActivityWithoutRetryPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Activity content = (Activity) results.getResult(); + turnContext.sendActivity(content).join(); + } + return CompletableFuture.completedFuture(null); + }); + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please send an event.") + .send(eventActivity) + .assertReply("2") + .startTest(); + } + + @Test + public void OnPromptErrorsWithNullContext() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", + new BasicActivityPromptValidator()); + + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + options.setPrompt(activity); + eventPrompt.onPromptNullContext(options).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OnPromptErrorsWithNullOptions() { + Assert.assertThrows( + IllegalArgumentException.class, () -> { + try { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityWithoutRetryPrompt", + new BasicActivityPromptValidator()); + dialogs.add(eventPrompt); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + return eventPrompt.onPromptNullOptions(dc); + }); + + new TestFlow(adapter, botLogic) + .send("hello") + .startTest().join(); + + } catch (CompletionException ex) { + throw ex.getCause().getCause(); + } + }); + } + + class BasicActivityPromptValidator implements PromptValidator<Activity> { + + BasicActivityPromptValidator(){ + + } + + @Override + public CompletableFuture<Boolean> promptValidator(PromptValidatorContext<Activity> promptContext) { + Assert.assertTrue(promptContext.getAttemptCount() > 0); + + Activity activity = promptContext.getRecognized().getValue(); + if (activity.getType() == ActivityTypes.EVENT) { + if ((int) activity.getValue() == 2) { + promptContext.getRecognized().setValue(MessageFactory.text(activity.getValue().toString())); + return CompletableFuture.completedFuture(true); + } + } else { + promptContext.getContext().sendActivity("Please send an 'event'-type Activity with a value of 2."); + } + + return CompletableFuture.completedFuture(false); + } + } + + class EmptyPromptValidator implements PromptValidator<Activity> { + + EmptyPromptValidator(){ + + } + + @Override + public CompletableFuture<Boolean> promptValidator(PromptValidatorContext<Activity> promptContext) { + return CompletableFuture.completedFuture(false); + } + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java new file mode 100644 index 000000000..508ca84ad --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; + +import org.junit.Assert; +import org.junit.Test; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +public class AttachmentPromptTests { + @Test + public void AttachmentPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new AttachmentPrompt("")); + } + + @Test + public void AttachmentPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new AttachmentPrompt(null)); + } + + @Test + public void BasicAttachmentPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("BasicAttachmentPrompt","","")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add attachment prompt to DialogSet. + AttachmentPrompt attachmentPrompt = new AttachmentPrompt("AttachmentPrompt"); + dialogs.add(attachmentPrompt); + + // Create mock attachment for testing. + Attachment attachment = new Attachment(); + attachment.setContent("some content"); + attachment.setContentType("text/plain"); + + // Create incoming activity with attachment. + Activity activityWithAttachment = new Activity(ActivityTypes.MESSAGE); + ArrayList<Attachment> attachmentList = new ArrayList<Attachment>(); + attachmentList.add(attachment); + activityWithAttachment.setAttachments(attachmentList); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please add an attachment."); + dc.prompt("AttachmentPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<Attachment> attachments = (ArrayList<Attachment>) results.getResult(); + Activity content = MessageFactory.text((String) attachments.get(0).getContent()); + turnContext.sendActivity(content).join(); + } + + return CompletableFuture.completedFuture(null); + }); + + + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please add an attachment.") + .send(activityWithAttachment) + .assertReply("some content") + .startTest(); + } + + @Test + public void RetryAttachmentPrompt() { + + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("RetryAttachmentPrompt","","")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add attachment prompt to DialogSet. + AttachmentPrompt attachmentPrompt = new AttachmentPrompt("AttachmentPrompt"); + dialogs.add(attachmentPrompt); + + // Create mock attachment for testing. + Attachment attachment = new Attachment(); + attachment.setContent("some content"); + attachment.setContentType("text/plain"); + + // Create incoming activity with attachment. + Activity activityWithAttachment = new Activity(ActivityTypes.MESSAGE); + ArrayList<Attachment> attachmentList = new ArrayList<Attachment>(); + attachmentList.add(attachment); + activityWithAttachment.setAttachments(attachmentList); + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please add an attachment."); + dc.prompt("AttachmentPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<Attachment> attachments = (ArrayList<Attachment>) results.getResult(); + Activity content = MessageFactory.text((String) attachments.get(0).getContent()); + turnContext.sendActivity(content).join(); + } + return CompletableFuture.completedFuture(null); + }); + + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please add an attachment.") + .send("hello again") + .assertReply("please add an attachment.") + .send(activityWithAttachment) + .assertReply("some content") + .startTest(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java new file mode 100644 index 000000000..6d5bd1f7a --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.TestLocale; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ChoicePromptLocaleVariationTests { + + String testCulture; + String inlineOr; + String inlineOrMore; + String separator; + + public ChoicePromptLocaleVariationTests(String testCulture, String inlineOr, String inlineOrMore, String separator) { + this.testCulture = testCulture; + this.inlineOr = inlineOr; + this.inlineOrMore = inlineOrMore; + this.separator = separator; + } + public static List<Object[]> getLocaleVariationTest() { + TestLocale[] testLocales = new TestLocale[2]; + testLocales[0] = new TestLocale(PromptCultureModels.ENGLISH, null, null, null); + testLocales[1] = new TestLocale(PromptCultureModels.SPANISH, null, null, null); + // testLocales[2] = new TestLocale(PromptCultureModels.DUTCH, null, null, null); + // testLocales[3] = new TestLocale(PromptCultureModels.BULGARIAN, null, null, null); + // testLocales[4] = new TestLocale(PromptCultureModels.FRENCH, null, null, null); + // testLocales[5] = new TestLocale(PromptCultureModels.HINDI, null, null, null); + // testLocales[6] = new TestLocale(PromptCultureModels.ITALIAN, null, null, null); + // testLocales[7] = new TestLocale(PromptCultureModels.JAPANESE, null, null, null); + // testLocales[8] = new TestLocale(PromptCultureModels.KOREAN, null, null, null); + // testLocales[9] = new TestLocale(PromptCultureModels.PORTUGUESE, null, null, null); + // testLocales[10] = new TestLocale(PromptCultureModels.CHINESE, null, null, null); + // testLocales[11] = new TestLocale(PromptCultureModels.SWEDISH, null, null, null); + // testLocales[12] = new TestLocale(PromptCultureModels.TURKISH, null, null, null); + + List<Object[]> resultList = new ArrayList<Object[]>(); + for (TestLocale testLocale : testLocales) { + resultList.add(new Object[] {testLocale.getValidLocale(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getCapEnding(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getTitleEnding(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getCapTwoLetter(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getLowerTwoLetter(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + } + + return resultList; + } + + + @Parameterized.Parameters + public static List<Object[]> data() { + return getLocaleVariationTest(); + + } + + private static List<Choice> colorChoices = Arrays.asList(new Choice("red"), new Choice("green"), + new Choice("blue")); + + @Test + public void testShouldRecognizeLocaleVariationsOfCorrectLocales() { + Assert.assertEquals(1, 1); + System.out.println("Testing: " + testCulture); + ShouldRecognizeLocaleVariationsOfCorrectLocales(this.testCulture, this.inlineOr, + this.inlineOrMore, this.separator); + } + + public void ShouldRecognizeLocaleVariationsOfCorrectLocales(String testCulture, String inlineOr, + String inlineOrMore, String separator) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, testCulture); + + dialogs.add(listPrompt); + + Activity helloLocale = MessageFactory.text("hello"); + helloLocale.setLocale(testCulture); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }).send(helloLocale).assertReply((activity) -> { + // Use ChoiceFactory to build the expected answer, manually + ChoiceFactoryOptions testChoiceOption = new ChoiceFactoryOptions(); + testChoiceOption.setInlineOr(inlineOr); + testChoiceOption.setInlineOrMore(inlineOrMore); + testChoiceOption.setInlineSeparator(separator); + + String expectedChoices = ChoiceFactory.inline(colorChoices, null, null, testChoiceOption).getText(); + Assert.assertEquals(String.format("favorite color?%s", expectedChoices), activity.getText()); + }).startTest().join(); + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java new file mode 100644 index 000000000..4d99f3d9d --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java @@ -0,0 +1,833 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IllformedLocaleException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.SuggestedActions; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Assert; +import org.junit.Test; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +public class ChoicePromptTests { + private static List<Choice> colorChoices = Arrays.asList(new Choice("red"), new Choice("green"), + new Choice("blue")); + + @Test + public void ChoicePromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ChoicePrompt("", null, null)); + } + + @Test + public void ChoicePromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ChoicePrompt(null, null, null)); + } + + @Test + public void ChoicePromptWithCardActionAndNoValueShouldNotFail() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt eventPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + Choice choice = new Choice(); + CardAction action = new CardAction(); + action.setType(ActionTypes.IM_BACK); + action.setValue("value"); + action.setTitle("title"); + choice.setAction(action); + + PromptOptions options = new PromptOptions(); + List<Choice> choiceList = new ArrayList<Choice>(); + choiceList.add(choice); + options.setChoices(choiceList); + + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith(" (1) title")) + .startTest() + .join(); + } + + @Test + public void ShouldSendPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt eventPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptAsAnInlineList() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt eventPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("favorite color? (1) red, (2) green, or (3) blue") + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptAsANumberedList() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.LIST); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("favorite color?\n\n 1. red\n 2. green\n 3. blue") + .startTest(); + } + + @Test + public void ShouldSendPromptUsingSuggestedActions() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.SUGGESTED_ACTION); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(new + CardAction[] + { + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + } + }))) + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptUsingHeroCard() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.HEROCARD); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateHeroCard( + new HeroCard() { + { + setText("favorite color?"); + setButtons(new ArrayList<CardAction>() { + { + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + }); + } + }); + } + }, 0)) + .startTest().join(); + } + + @Test + public void ShouldSendPromptUsingAppendedHeroCard() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.HEROCARD); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + Attachment attachment = new Attachment(); + attachment.setContent("some content"); + attachment.setContentType("text/plain"); + + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + activity.setAttachments(new ArrayList<Attachment>() { { add(attachment); } }); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateHeroCard( + new HeroCard() { + { + setText("favorite color?"); + setButtons(new ArrayList<CardAction>() { + { + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + }); + } + }); + } + }, 1)) + .startTest().join(); + } + + @Test + public void ShouldSendPromptWithoutAddingAList() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("favorite color?") + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptWithoutAddingAListButAddingSsml() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + activity.setSpeak("spoken prompt"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateSpeak("favorite color?", "spoken prompt")) + .startTest() + .join(); + } + + @Test + public void ShouldRecognizeAChoice() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + FoundChoice choiceResult = (FoundChoice) results.getResult(); + turnContext.sendActivities(MessageFactory.text(choiceResult.getValue())).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .send("red") + .assertReply("red") + .startTest() + .join(); + } + + // This is being left out for now due to it failing due to an issue in the Text Recognizers library. + // It should be worked out in the recognizers and then this test should be enabled again. + //@Test + public void ShouldNotRecognizeOtherText() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("your favorite color, please?"); + options.setRetryPrompt(retryActivity); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .send("what was that?") + .assertReply("your favorite color, please?") + .startTest() + .join(); + } + + @Test + public void ShouldCallCustomValidator() { + + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<FoundChoice> validator = (promptContext) -> { + promptContext.getContext().sendActivity(MessageFactory.text("validator called")); + return CompletableFuture.completedFuture(true); + }; + + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", validator, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .send("I'll take the red please.") + .assertReply("validator called") + .startTest() + .join(); + } + + @Test + public void ShouldUseChoiceStyleIfPresent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.HEROCARD); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + options.setStyle(ListStyle.SUGGESTED_ACTION); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(new + CardAction[] + { + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + } + }))) + .startTest() + .join(); + } + + @Test + public void ShouldDefaultToEnglishLocaleNull() { + PerformShouldDefaultToEnglishLocale(null); + } + + @Test + public void ShouldDefaultToEnglishLocaleEmptyString() { + PerformShouldDefaultToEnglishLocale(""); + } + + @Test + public void ShouldDefaultToEnglishLocaleNotSupported() { + Assert.assertThrows(IllformedLocaleException.class, () -> { + try { + PerformShouldDefaultToEnglishLocale("not-supported"); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + public void PerformShouldDefaultToEnglishLocale(String locale) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + + dialogs.add(listPrompt); + + Activity helloLocale = MessageFactory.text("hello"); + helloLocale.setLocale(locale); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send(helloLocale) + .assertReply((activity) -> { + // Use ChoiceFactory to build the expected answer, manually + ChoiceFactoryOptions testChoiceOption = new ChoiceFactoryOptions(); + testChoiceOption.setInlineOr(PromptCultureModels.ENGLISH.getInlineOr()); + testChoiceOption.setInlineOrMore(PromptCultureModels.ENGLISH.getInlineOrMore()); + testChoiceOption.setInlineSeparator(PromptCultureModels.ENGLISH.getSeparator()); + + String expectedChoices = ChoiceFactory.inline(colorChoices, null, null, testChoiceOption).getText(); + Assert.assertEquals(String.format("favorite color?%s", expectedChoices), activity.getText()); + }) + .startTest() + .join(); + } + + @Test + public void ShouldAcceptAndRecognizeCustomLocaleDict() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + PromptCultureModel culture = new PromptCultureModel() { + { + setInlineOr(" customOr "); + setInlineOrMore(" customOrMore "); + setLocale("custom-custom"); + setSeparator("customSeparator"); + setNoInLanguage("customNo"); + setYesInLanguage("customYes"); + } + }; + + Map<String, ChoiceFactoryOptions> customDict = new HashMap<String, ChoiceFactoryOptions>(); + ChoiceFactoryOptions choiceOption = new ChoiceFactoryOptions(culture.getSeparator(), + culture.getInlineOr(), + culture.getInlineOrMore(), + true); + customDict.put(culture.getLocale(), choiceOption); + + dialogs.add(new ChoicePrompt("ChoicePrompt", customDict, null, culture.getLocale())); + + Activity helloLocale = MessageFactory.text("hello"); + helloLocale.setLocale(culture.getLocale()); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + activity.setLocale(culture.getLocale()); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send(helloLocale) + .assertReply((activity) -> { + // Use ChoiceFactory to build the expected answer, manually + ChoiceFactoryOptions testChoiceOption = new ChoiceFactoryOptions(culture.getSeparator(), + culture.getInlineOr(), + culture.getInlineOrMore(), + true); + + String expectedChoices = ChoiceFactory.inline(colorChoices, null, null, testChoiceOption).getText(); + Assert.assertEquals(String.format("favorite color?%s", expectedChoices), activity.getText()); + }) + .startTest() + .join(); + } + + class Validators { + public Validators() { + + } + private Consumer<Activity> validedStartsWith(String expected) { + return activity -> { + //Assert.IsAssignableFrom<MessageActivity>(activity); + Activity msg = (Activity) activity; + Assert.assertTrue(msg.getText().startsWith(expected)); + }; + } + + private Consumer<Activity> validateSuggestedActions(String expectedText, + SuggestedActions expectedSuggestedActions) { + return activity -> { + //Assert.IsAssignableFrom<MessageActivity>(activity); + Assert.assertEquals(expectedText, activity.getText()); + Assert.assertEquals(expectedSuggestedActions.getActions().size(), + activity.getSuggestedActions().getActions().size()); + + for (int i = 0; i < expectedSuggestedActions.getActions().size(); i++) { + Assert.assertEquals(expectedSuggestedActions.getActions().get(i).getType(), + activity.getSuggestedActions().getActions().get(i).getType()); + Assert.assertEquals(expectedSuggestedActions.getActions().get(i).getValue(), + activity.getSuggestedActions().getActions().get(i).getValue()); + Assert.assertEquals(expectedSuggestedActions.getActions().get(i).getTitle(), + activity.getSuggestedActions().getActions().get(i).getTitle()); + } + }; + } + + private Consumer<Activity> validateHeroCard(HeroCard expectedHeroCard, int index) { + return activity -> { + HeroCard attachedHeroCard = (HeroCard) activity.getAttachments().get(index).getContent(); + + Assert.assertEquals(expectedHeroCard.getTitle(), attachedHeroCard.getTitle()); + Assert.assertEquals(expectedHeroCard.getButtons().size(), attachedHeroCard.getButtons().size()); + for (int i = 0; i < expectedHeroCard.getButtons().size(); i++) { + Assert.assertEquals(expectedHeroCard.getButtons().get(i).getType(), + attachedHeroCard.getButtons().get(i).getType()); + Assert.assertEquals(expectedHeroCard.getButtons().get(i).getValue(), + attachedHeroCard.getButtons().get(i).getValue()); + Assert.assertEquals(expectedHeroCard.getButtons().get(i).getTitle(), + attachedHeroCard.getButtons().get(i).getTitle()); + } + }; + } + + private Consumer<Activity> validateSpeak(String expectedText, String expectedSpeak) { + return activity -> { + Activity msg = (Activity) activity; + Assert.assertEquals(expectedText, msg.getText()); + Assert.assertEquals(expectedSpeak, msg.getSpeak()); + }; + } + + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java new file mode 100644 index 000000000..51631bd8f --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.javatuples.Triplet; +import org.junit.Test; + +public class ConfirmPromptLocTests { + + @Test + public void ConfirmPrompt_Activity_Locale_Default() { + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Illegal_Default() { + ConfirmPrompt_Locale_Impl(null, null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(null, "", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(null, "not-supported", "(1) Yes or (2) No", "Yes", "1"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Default_Number() { + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "2", "0"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "2", "0"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Illegal_Default_Number() { + ConfirmPrompt_Locale_Impl(null, null, "(1) Yes or (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, "", "(1) Yes or (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, "not-supported", "(1) Yes or (2) No", "1", "1"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Activity() { + ConfirmPrompt_Locale_Impl(Culture.English, null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(Culture.English, null, "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl(Culture.Spanish, null, "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl(Culture.Spanish, null, "(1) Sí o (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Illegal_Activity() { + ConfirmPrompt_Locale_Impl(null, null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("", null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("not-supported", null, "(1) Yes or (2) No", "Yes", "1"); + } + + @Test + public void ConfirmPrompt_Locale_Variations_English() { + ConfirmPrompt_Locale_Impl("en-us", "en-us", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en-us", "en-us", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("en-US", "en-US", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en-US", "en-US", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("en-Us", "en-Us", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en-Us", "en-Us", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("EN", "EN", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("EN", "EN", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("en", "en", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en", "en", "(1) Yes or (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Locale_Variations_Spanish() { + ConfirmPrompt_Locale_Impl("es-es", "es-es", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es-es", "es-es", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("es-ES", "es-ES", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es-ES", "es-ES", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("es-Es", "es-Es", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es-Es", "es-Es", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("ES", "ES", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("ES", "ES", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("es", "es", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es", "es", "(1) Sí o (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Locale_Override_ChoiceDefaults() { + ConfirmPrompt_Locale_Override_ChoiceDefaults("custom-custom", "(1) customYes customOr (2) customNo", + "customYes", "1"); + ConfirmPrompt_Locale_Override_ChoiceDefaults("custom-custom", "(1) customYes customOr (2) customNo", "customNo", + "0"); + } + + public void ConfirmPrompt_Locale_Override_ChoiceDefaults(String defaultLocale, String prompt, String utterance, + String expectedResponse) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + PromptCultureModel culture = new PromptCultureModel(); + culture.setInlineOr(" customOr "); + culture.setInlineOrMore(" customOrMore "); + culture.setLocale("custom-custom"); + culture.setSeparator("customSeparator"); + culture.setNoInLanguage("customNo"); + culture.setYesInLanguage("customYes"); + + Map<String, Triplet<Choice, Choice, ChoiceFactoryOptions>> customDict = new HashMap<String, Triplet<Choice, Choice, ChoiceFactoryOptions>>(); + customDict.put(culture.getLocale(), + new Triplet<Choice, Choice, ChoiceFactoryOptions>(new Choice(culture.getYesInLanguage()), + new Choice(culture.getNoInLanguage()), new ChoiceFactoryOptions(culture.getSeparator(), + culture.getInlineOr(), culture.getInlineOrMore(), true))); + // Prompt should default to English if locale is a non-supported value + dialogs.add(new ConfirmPrompt("ConfirmPrompt", customDict, null, defaultLocale)); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Prompt."); + options.setPrompt(activity); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("1")); + } else { + turnContext.sendActivity(MessageFactory.text("0")); + } + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Prompt. " + prompt).send(utterance).assertReply(expectedResponse).startTest() + .join(); + } + + private void ConfirmPrompt_Locale_Impl(String activityLocale, String defaultLocale, String prompt, String utterance, + String expectedResponse) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("ConfirmPrompt_Locale_Impl", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Prompt should default to English if locale is a non-supported value + dialogs.add(new ConfirmPrompt("ConfirmPrompt", null, null, defaultLocale)); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Prompt."); + options.setPrompt(activity); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("1")); + } else { + turnContext.sendActivity(MessageFactory.text("0")); + } + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Prompt. " + prompt).send(utterance).assertReply(expectedResponse).startTest(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java new file mode 100644 index 000000000..d017e8996 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java @@ -0,0 +1,390 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Assert; +import org.junit.Test; + +public class ConfirmPromptTests { + + @Test + public void ChoicePromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ConfirmPrompt("")); + } + + @Test + public void ConfirmPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ConfirmPrompt(null)); + } + + @Test + public void ConfirmPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Please confirm."); + options.setPrompt(activity); + + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("yes") + .assertReply("Confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptRetry() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("lala") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("no") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptNoOptions() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(" (1) Yes or (2) No") + .send("lala") + .assertReply(" (1) Yes or (2) No") + .send("no") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptChoiceOptionsNumbers() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + ChoiceFactoryOptions choiceOptions = new ChoiceFactoryOptions(); + choiceOptions.setIncludeNumbers(true); + eventPrompt.setChoiceOptions(choiceOptions); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("lala") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("2") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptChoiceOptionsMultipleAttempts() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + ChoiceFactoryOptions choiceOptions = new ChoiceFactoryOptions(); + choiceOptions.setIncludeNumbers(true); + eventPrompt.setChoiceOptions(choiceOptions); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("lala") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("what") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("2") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptChoiceOptionsNoNumbers() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + ChoiceFactoryOptions choiceOptions = new ChoiceFactoryOptions(); + choiceOptions.setIncludeNumbers(false); + choiceOptions.setInlineSeparator("~"); + eventPrompt.setChoiceOptions(choiceOptions); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. Yes or No") + .send("2") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. Yes or No") + .send("no") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ShouldUsePromptClassStyleProperty() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("is it true?"); + options.setPrompt(activity); + + dc.prompt("ConfirmPrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("is it true? (1) Yes or (2) No") + .startTest() + .join(); + } + + @Test + public void PromptOptionsStyleShouldOverridePromptClassStyleProperty() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("is it true?"); + options.setPrompt(activity); + options.setStyle(ListStyle.NONE); + dc.prompt("ConfirmPrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("is it true?") + .startTest() + .join(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java new file mode 100644 index 000000000..bae5a24e4 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Test; + +public class DateTimePromptTests { + + @Test + public void BasicDateTimePrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + DateTimePrompt dateTimePrompt = new DateTimePrompt("DateTimePrompt", null, Culture.English); + // Create and add number prompt to DialogSet. + dialogs.add(dateTimePrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("What date would you like?"); + options.setPrompt(activity); + dc.prompt("DateTimePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<DateTimeResolution> resolution = (ArrayList<DateTimeResolution>) results.getResult(); + //Activity reply = MessageFactory.text($"Timex:'{resolution.Timex}' Value:'{resolution.Value}'"); + Activity reply = MessageFactory.text(String.format("Timex:'%s' Value:'%s'", + resolution.get(0).getTimex(), + resolution.get(0).getValue())); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("What date would you like?") + .send("5th December 2018 at 9am") + .assertReply("Timex:'2018-12-05T09' Value:'2018-12-05 09:00:00'") + .startTest() + .join(); + } + + @Test + public void MultipleResolutionsDateTimePrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + DateTimePrompt dateTimePrompt = new DateTimePrompt("DateTimePrompt", null, Culture.English); + // Create and add number prompt to DialogSet. + dialogs.add(dateTimePrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("What date would you like?"); + options.setPrompt(activity); + dc.prompt("DateTimePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<DateTimeResolution> resolution = (ArrayList<DateTimeResolution>) results.getResult(); + + List<String> elements = new ArrayList<String>(); + + for (DateTimeResolution dateTimeResolution : resolution) { + if (!elements.contains(dateTimeResolution.getTimex())) { + elements.add(dateTimeResolution.getTimex()); + } + } + Activity reply = MessageFactory.text(String.join(" ", elements)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("What date would you like?") + .send("Wednesday 4 oclock") + .assertReply("XXXX-WXX-3T04 XXXX-WXX-3T16") + .startTest() + .join(); + } + + @Test + public void DateTimePromptWithValidator() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("DateTimePromptWithValidator", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add custom activity prompt to DialogSet. + DateTimePrompt dateTimePrompt = new DateTimePrompt("DateTimePrompt", + new DateTimeValidator(), Culture.English); + // Create and add number prompt to DialogSet. + dialogs.add(dateTimePrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("What date would you like?"); + options.setPrompt(activity); + dc.prompt("DateTimePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<DateTimeResolution> resolution = (ArrayList<DateTimeResolution>) results.getResult(); + //Activity reply = MessageFactory.text($"Timex:'{resolution.Timex}' Value:'{resolution.Value}'"); + Activity reply = MessageFactory.text(String.format("Timex:'%s' Value:'%s'", + resolution.get(0).getTimex(), + resolution.get(0).getValue())); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("What date would you like?") + .send("5th December 2018 at 9am") + .assertReply("Timex:'2018-12-05' Value:'2018-12-05'") + .startTest() + .join(); + } + + class DateTimeValidator implements PromptValidator<List<DateTimeResolution>> { + + DateTimeValidator(){ + } + + @Override + public CompletableFuture<Boolean> promptValidator(PromptValidatorContext<List<DateTimeResolution>> prompt) { + if (prompt.getRecognized().getSucceeded()) { + DateTimeResolution resolution = prompt.getRecognized().getValue().get(0); + + // re-write the resolution to just include the date part. + DateTimeResolution rewrittenResolution = new DateTimeResolution(); + rewrittenResolution.setTimex(resolution.getTimex().split("T")[0]); + rewrittenResolution.setValue(resolution.getValue().split(" ")[0]); + + List<DateTimeResolution> valueList = new ArrayList<DateTimeResolution>(); + valueList.add(rewrittenResolution); + prompt.getRecognized().setValue(valueList); + return CompletableFuture.completedFuture(true); + } + + return CompletableFuture.completedFuture(false); + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java new file mode 100644 index 000000000..844916fd9 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.schema.Activity; + +public class EventActivityPrompt extends ActivityPrompt { + + public EventActivityPrompt(String dialogId, PromptValidator<Activity> activityPromptTestValidator) { + super(dialogId, activityPromptTestValidator); + } + + public CompletableFuture<Void> onPromptNullContext(Object options) { + PromptOptions opt = (PromptOptions) options; + // should throw ArgumentNullException + return super.onPrompt(null, null, opt, false); + } + + public CompletableFuture<Void> onPromptNullOptions(DialogContext dc) { + // should throw ArgumentNullException + return super.onPrompt(dc.getContext(), null, null, false); + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java new file mode 100644 index 000000000..6d3659faa --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.dialogs.DialogContext; + +public class NumberPromptMock extends NumberPrompt<Integer> { + + public NumberPromptMock(String dialogId, PromptValidator<Integer> validator, String defaultLocale) + throws UnsupportedDataTypeException { + super(dialogId, validator, defaultLocale, Integer.class); + } + + public CompletableFuture<Void> onPromptNullContext(Object options) { + PromptOptions opt = (PromptOptions) options; + + // should throw ArgumentNullException + return onPrompt(null, null, opt, false); + } + + public CompletableFuture<Void> onPromptNullOptions(DialogContext dc) { + // should throw ArgumentNullException + return onPrompt(dc.getContext(), null, null, false); + } + + public CompletableFuture<Void> onRecognizeNullContext() { + // should throw ArgumentNullException + onRecognize(null, null, null).join(); + return CompletableFuture.completedFuture(null); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java new file mode 100644 index 000000000..3ea7723a0 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java @@ -0,0 +1,635 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Assert; +import org.junit.Test; + +public class NumberPromptTests { + + @Test + public void NumberPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new NumberPrompt<Integer>("", Integer.class)); + } + + @Test + public void NumberPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new NumberPrompt<Integer>(null, Integer.class)); + } + + @Test + public void NumberPromptWithUnsupportedTypeShouldFail() { + Assert.assertThrows(UnsupportedDataTypeException.class, () -> new NumberPrompt<Short>("prompt", Short.class)); + } + + @Test + public void NumberPromptWithNullTurnContextShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + NumberPromptMock numberPromptMock = new NumberPromptMock("NumberPromptMock", null, null); + + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Please send a number."); + options.setPrompt(activity); + numberPromptMock.onPromptNullContext(options).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OnPromptErrorsWithNullOptions() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + NumberPromptMock numberPromptMock = new NumberPromptMock("NumberPromptMock", null, null); + + dialogs.add(numberPromptMock); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + numberPromptMock.onPromptNullOptions(dc).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .startTest() + .join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OnRecognizeWithNullTurnContextShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + NumberPromptMock numberPromptMock = new NumberPromptMock("NumberPromptMock", null, null); + numberPromptMock.onRecognizeNullContext(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void NumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Integer> numberPrompt = new NumberPrompt<Integer>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Integer.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int numberResult = (int) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Bot received the number '42'.") + .startTest() + .join(); + } + + @Test + public void NumberPromptRetry() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Integer> numberPrompt = new NumberPrompt<Integer>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Integer.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("You must enter a number."); + options.setRetryPrompt(retryActivity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int numberResult = (int) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("hello") + .assertReply("You must enter a number.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void NumberPromptValidator() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<Integer> validator = (promptContext) -> { + Integer result = promptContext.getRecognized().getValue(); + + if (result < 100 && result > 0) { + return CompletableFuture.completedFuture(true); + } + + return CompletableFuture.completedFuture(false); + }; + + // Create and add number prompt to DialogSet. + NumberPrompt<Integer> numberPrompt = new NumberPrompt<Integer>("NumberPrompt", validator, + PromptCultureModels.ENGLISH_CULTURE, Integer.class); + dialogs.add(numberPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("You must enter a positive number less than 100."); + options.setRetryPrompt(retryActivity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int numberResult = (int) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("150") + .assertReply("You must enter a positive number less than 100.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void FloatNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Float> numberPrompt = new NumberPrompt<Float>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Float.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Float numberResult = (Float) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3.14") + .assertReply("Bot received the number '3.14'.") + .startTest() + .join(); + } + + @Test + public void LongNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Long> numberPrompt = new NumberPrompt<Long>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Long.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Long numberResult = (Long) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Bot received the number '42'.") + .startTest() + .join(); + } + + @Test + public void DoubleNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3.14") + .assertReply("Bot received the number '3.14'.") + .startTest() + .join(); + } + + @Test + public void CurrencyNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) .send("hello") + .assertReply("Enter a number.") + .send("$500") + .assertReply("Bot received the number '500'.") + .startTest() + .join(); + } + + @Test + public void AgeNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("i am 18 years old") + .assertReply("Bot received the number '18'.") + .startTest() + .join(); + } + + @Test + public void DimensionNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("I've run 5km") + .assertReply("Bot received the number '5'.") + .startTest() + .join(); + } + + @Test + public void TemperatureNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("The temperature is 32C") + .assertReply("Bot received the number '32'.") + .startTest() + .join(); + } + + @Test + public void CultureThruNumberPromptCtor() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.DUTCH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + Assert.assertTrue(3.14 == numberResult); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3,14") + .startTest() + .join(); + } + + @Test + public void CultureThruActivityNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.DUTCH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + Assert.assertTrue(3.14 == numberResult); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send(new Activity() { + { + setType(ActivityTypes.MESSAGE); + setText("3,14"); + setLocale(PromptCultureModels.DUTCH_CULTURE); + } + }) + .startTest() + .join(); + } + + @Test + public void NumberPromptDefaultsToEnUsLocale() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, null, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3.14") + .assertReply("Bot received the number '3.14'.") + .startTest() + .join(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java new file mode 100644 index 000000000..1c06ab1ec --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IllformedLocaleException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.SuggestedActions; +import com.microsoft.recognizers.text.Culture; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +public class TextPromptTests { + + @Test + public void TextPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new TextPrompt("")); + } + + @Test + public void TextPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new TextPrompt(null)); + } + + @Test + public void TextPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("textPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt"); + + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("some text") + .assertReply("Bot received the text 'some text'.").startTest().join(); + } + + @Test + public void TextPromptWithNaughtyStrings() throws FileNotFoundException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("textPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt"); + + dialogs.add(textPrompt); + File f = new File(ClassLoader.getSystemClassLoader().getResource("naughtyStrings.txt").getFile()); + + FileReader fr = new FileReader(f); + BufferedReader br = new BufferedReader(fr); + String naughtyString = ""; + do { + naughtyString = GetNextNaughtyString(br); + try { + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(textResult); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter some text.") + .send(naughtyString) + .assertReply(naughtyString) + .startTest() + .join(); + } + catch (Exception e) { + // If the input message is empty after a .Trim() operation, character the comparison will fail + // because the reply message will be a Message Activity with null as Text, this is expected behavior + String message = e.getMessage(); + boolean messageIsBlank = e.getMessage() + .contains("should match expected") + && naughtyString.equals(" "); + boolean messageIsEmpty = e.getMessage().contains("should match expected") + && StringUtils.isBlank(naughtyString); + if (!(messageIsBlank || messageIsEmpty)) { + throw e; + } + } + } + while (!StringUtils.isEmpty(naughtyString)); + try { + fr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void TextPromptValidator() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("TextPromptValidator"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + PromptValidator<String> validator = (promptContext) -> { + String value = promptContext.getRecognized().getValue(); + if (value.length() <= 3) { + promptContext.getContext() + .sendActivity(MessageFactory.text("Make sure the text is greater than three characters.")); + return CompletableFuture.completedFuture(false); + } else { + return CompletableFuture.completedFuture(true); + } + }; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt", validator); + // Create and add number prompt to DialogSet. + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("hi") + .assertReply("Make sure the text is greater than three characters.").send("hello") + .assertReply("Bot received the text 'hello'.").startTest().join(); + } + + @Test + public void TextPromptWithRetryPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("TextPromptWithRetryPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + PromptValidator<String> validator = (promptContext) -> { + String value = promptContext.getRecognized().getValue(); + if (value.length() >= 3) { + return CompletableFuture.completedFuture(true); + } else { + return CompletableFuture.completedFuture(false); + } + }; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt", validator); + // Create and add number prompt to DialogSet. + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Make sure the text is greater than three characters."); + options.setRetryPrompt(retryActivity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("hi") + .assertReply("Make sure the text is greater than three characters.").send("hello") + .assertReply("Bot received the text 'hello'.").startTest().join(); + } + + @Test + public void TextPromptValidatorWithMessageShouldNotSendRetryPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState + .createProperty("TextPromptValidatorWithMessageShouldNotSendRetryPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + PromptValidator<String> validator = (promptContext) -> { + String value = promptContext.getRecognized().getValue(); + if (value.length() <= 3) { + promptContext.getContext() + .sendActivity(MessageFactory.text("The text should be greater than 3 chars.")); + return CompletableFuture.completedFuture(false); + } else { + return CompletableFuture.completedFuture(true); + } + }; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt", validator); + // Create and add number prompt to DialogSet. + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Make sure the text is greater than three characters."); + options.setRetryPrompt(retryActivity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("hi") + .assertReply("The text should be greater than 3 chars.").send("hello") + .assertReply("Bot received the text 'hello'.").startTest().join(); + } + + private static String GetNextNaughtyString(BufferedReader reader) { + String textLine; + try { + while ((textLine = reader.readLine()) != null) { + if (!textLine.startsWith("#")) { + return textLine; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + return ""; + } +} + diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java index e2b0b4223..cb0ff880d 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.integration; import org.slf4j.LoggerFactory; @@ -34,7 +37,7 @@ public ClasspathPropertiesConfiguration() { /** * Returns a value for the specified property name. - * + * * @param key The property name. * @return The property value. */ @@ -42,4 +45,12 @@ public ClasspathPropertiesConfiguration() { public String getProperty(String key) { return properties.getProperty(key); } + + /** + * @return The Properties value. + */ + @Override + public Properties getProperties() { + return this.properties; + } } diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java index 07a7bac93..dda35a4a8 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java @@ -3,15 +3,24 @@ package com.microsoft.bot.integration; +import java.util.Properties; + /** * Provides read-only access to configuration properties. */ public interface Configuration { /** * Returns a value for the specified property name. - * + * * @param key The property name. * @return The property value. */ String getProperty(String key); + + /** + * Returns the Properties in the Configuration. + * + * @return The Properties in the Configuration. + */ + Properties getProperties(); } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java index 06151c02f..73f9466ec 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java @@ -26,6 +26,10 @@ private Serialization() { objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.findAndRegisterModules(); + + // NOTE: Undetermined if we should accommodate non-public fields. The normal + // Bean pattern, and Jackson default, is for public fields or accessors. + //objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); } /** @@ -62,6 +66,48 @@ public static <T> T safeGetAs(Object obj, Class<T> classType) throws JsonProcess return objectMapper.treeToValue(node, classType); } + + /** + * @param obj The Object to clone + * @return Object The cloned Object + */ + public static Object clone(Object obj) { + if (obj == null) { + return null; + } + + JsonNode node = objectMapper.valueToTree(obj); + try { + return objectMapper.treeToValue(node, obj.getClass()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @param <T> The Type of the Class + * @param src The source JsonNode + * @param cls The Class to Map + * @return the result of the mapping + */ + public static <T> T treeToValue(JsonNode src, Class<T> cls) { + try { + return objectMapper.treeToValue(src, cls); + } catch (JsonProcessingException e) { + return null; + } + } + + /** + * Convert Object to JsonNode. + * @param obj The object to convert. + * @return The JsonNode for the object tree. + */ + public static JsonNode objectToTree(Object obj) { + return objectMapper.valueToTree(obj); + } + /** * Deserializes an object to a type as a future to ease CompletableFuture * chaining. @@ -117,4 +163,77 @@ public static String toString(Object source) throws JsonProcessingException { public static JsonNode jsonToTree(String json) throws IOException { return objectMapper.readTree(json); } + + + /** + * @param s The string to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(String s) { + return objectMapper.getNodeFactory().textNode(s); + } + + + /** + * @param i The int to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(int i) { + return objectMapper.getNodeFactory().numberNode(i); + } + + + /** + * @param l The long to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(long l) { + return objectMapper.getNodeFactory().numberNode(l); + } + + + /** + * @param f The float to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(float f) { + return objectMapper.getNodeFactory().numberNode(f); + } + + + /** + * @param d The double to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(double d) { + return objectMapper.getNodeFactory().numberNode(d); + } + + + /** + * @param s The short to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(short s) { + return objectMapper.getNodeFactory().numberNode(s); + } + + + /** + * @param b The boolean to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(boolean b) { + return objectMapper.getNodeFactory().booleanNode(b); + } + + + /** + * @param b The byte to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(byte b) { + return objectMapper.getNodeFactory().numberNode(b); + } } + diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java index b5037fd47..164cbdb39 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema; /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java index 70e5c44b3..5297b20a6 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema.teams; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java index 61bf75290..61f15ff75 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java index 618cc930f..4e56f9905 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema; import org.junit.Assert; diff --git a/pom.xml b/pom.xml index c1d6d2b91..303a30fa7 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <checkstyle.version>3.1.0</checkstyle.version> - <pmd.version>3.12.0</pmd.version> + <pmd.version>3.13.0</pmd.version> <repo.id>MyGet</repo.id> <repo.url>https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/</repo.url> </properties> diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java index 0cd816047..bc874c9c0 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.sample.teamsactionpreview.models; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java index 8e48890e6..b614f22d4 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.sample.teamsactionpreview.models; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java index 9385a5d82..bdb00d86b 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.sample.teamsactionpreview.models; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java index a08b27333..9cb519aba 100644 --- a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java +++ b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java @@ -8,7 +8,7 @@ import com.microsoft.bot.builder.MessageFactory; import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.teams.TeamsActivityHandler; -import com.microsoft.bot.integration.Async; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.integration.Configuration; import com.microsoft.bot.sample.teamstaskmodule.models.AdaptiveCardTaskFetchValue; import com.microsoft.bot.sample.teamstaskmodule.models.CardTaskFetchValue;