├── .github └── workflows │ └── pr_validation.yml ├── .gitignore ├── .run └── JS Regex.run.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_EXAMPLE.md ├── analysis_options.yaml ├── build.yaml ├── example └── nlp_example.dart ├── example_js_regex ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── integration_test │ ├── datetime │ │ ├── date_extractor_cases.dart │ │ ├── date_extractor_test.dart │ │ ├── date_parser_cases.dart │ │ ├── date_parser_test.dart │ │ ├── date_period_extractor_cases.dart │ │ ├── date_period_extractor_test.dart │ │ ├── date_period_parser_cases.dart │ │ ├── date_period_parser_test.dart │ │ ├── date_test.dart │ │ ├── date_time_extractor_cases.dart │ │ ├── date_time_extractor_test.dart │ │ ├── date_time_model_cases.dart │ │ ├── date_time_parser_cases.dart │ │ ├── date_time_parser_test.dart │ │ ├── date_time_period_extractor_cases.dart │ │ ├── date_time_period_extractor_test.dart │ │ ├── date_time_period_parser_cases.dart │ │ ├── date_time_period_parser_test.dart │ │ ├── date_time_test.dart │ │ ├── set_extractor_cases.dart │ │ ├── set_extractor_test.dart │ │ ├── set_parser_cases.dart │ │ └── set_parser_test.dart │ ├── duration │ │ ├── duration_extractor_cases.dart │ │ ├── duration_extractor_test.dart │ │ ├── duration_parser_cases.dart │ │ └── duration_parser_test.dart │ ├── infrastructure_test │ │ └── js_recognition_test.dart │ └── time │ │ ├── time_extractor_cases.dart │ │ ├── time_extractor_test.dart │ │ ├── time_parser_cases.dart │ │ ├── time_period_extractor_cases.dart │ │ ├── time_period_extractor_test.dart │ │ ├── time_period_parser_cases.dart │ │ ├── time_period_parser_test.dart │ │ ├── time_zone_extractor_cases.dart │ │ ├── time_zone_extractor_test.dart │ │ └── time_zone_parser_cases.dart ├── lib │ └── main.dart ├── pubspec.yaml ├── test_driver │ └── integration_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── nlp.dart └── src │ ├── core │ ├── cultures.dart │ ├── definition_loader.dart │ ├── english_merged_extractor.dart │ ├── extraction.dart │ ├── global_recognizer.dart │ ├── model_result.dart │ ├── number_format_utility.dart │ ├── parser.dart │ ├── range_timex_component.dart │ ├── string_matcher.dart │ ├── string_utility.dart │ ├── task_mode_processing.dart │ └── timex_utility.dart │ ├── date_time │ ├── ago_later_util.dart │ ├── base_date_extractor.dart │ ├── base_date_parser.dart │ ├── base_date_period_extractor.dart │ ├── base_date_period_parser.dart │ ├── base_date_time_period_extractor.dart │ ├── base_date_time_period_parser.dart │ ├── base_datetime_extractor.dart │ ├── base_datetime_options_configuration.dart │ ├── base_datetime_parser.dart │ ├── base_datetime_utility_configuration.dart │ ├── base_holiday_extractor.dart │ ├── base_merged_date_time_extractor.dart │ ├── base_merged_date_time_parser.dart │ ├── base_set_extractor.dart │ ├── base_set_parser.dart │ ├── base_time_extractor.dart │ ├── base_time_parser.dart │ ├── base_time_period_extractor.dart │ ├── base_time_period_parser.dart │ ├── constants.dart │ ├── data_structure.dart │ ├── date_context.dart │ ├── date_object_extension.dart │ ├── date_parser_configuration.dart │ ├── date_time_extraction.dart │ ├── date_time_format_util.dart │ ├── date_time_parsing.dart │ ├── date_time_recognizer.dart │ ├── date_util.dart │ ├── datetime_utility_configuration.dart │ ├── english │ │ ├── english_date_parser_configuration.dart │ │ ├── english_date_period_extractor_configuration.dart │ │ ├── english_date_period_parser_configuration.dart │ │ ├── english_date_time_extractor_configuration.dart │ │ ├── english_date_time_parser_configuration.dart │ │ ├── english_date_time_period_extractor_configuration.dart │ │ ├── english_date_time_period_parser_configuration.dart │ │ ├── english_date_time_utility_configuration.dart │ │ ├── english_holiday_extractor_configuration.dart │ │ ├── english_set_extractor_configuration.dart │ │ ├── english_set_parser_configuration.dart │ │ ├── english_time_extractor_configuration.dart │ │ ├── english_time_parser_configuration.dart │ │ ├── english_time_period_extractor_configuration.dart │ │ └── english_time_period_parser_configuration.dart │ ├── english_date_extractor.dart │ ├── english_date_period_extraction.dart │ ├── english_date_time.dart │ ├── english_date_time_parser.dart │ ├── extract_result_extension.dart │ ├── holiday_extractor_configuration.dart │ ├── merged_parser_util.dart │ ├── mod_and_date_result.dart │ ├── resolution.dart │ ├── set_handler.dart │ ├── task_mode_set_handler.dart │ ├── time_of_day_resolution_result.dart │ ├── time_period_functions.dart │ ├── timex_property.dart │ ├── timex_regex.dart │ ├── timex_resolver.dart │ └── timezone_utility.dart │ ├── duration │ ├── base_duration_parser.dart │ ├── duration.dart │ ├── duration_extractor.dart │ └── english_duration_parser_configuration.dart │ ├── numbers │ ├── base_number_parser.dart │ ├── english_number_parser_configuration.dart │ ├── number_constants.dart │ ├── number_with_unit_tokenizer.dart │ └── numbers.dart │ ├── regular_expressions │ ├── js_regexp.dart │ ├── matching_util.dart │ ├── regular_expressions_extensions.dart │ └── string_extensions.dart │ └── time │ ├── english_time_zone.dart │ ├── english_time_zone_extractor.dart │ └── time_zone_extractor.dart ├── pubspec.yaml └── test ├── cases ├── english │ ├── LICENSE │ ├── date_time.json │ └── date_time_test.dart ├── test_case_parsing_test.dart ├── test_case_serialization.dart └── test_case_serialization.g.dart └── infrastructure └── regular_expression_composition_test.dart /.github/workflows/pr_validation.yml: -------------------------------------------------------------------------------- 1 | name: Run tests for pull requests 2 | on: [pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | # Checkout the PR branch 8 | - uses: actions/checkout@v3 9 | 10 | # Setup chromedriver for integration tests 11 | - uses: nanasess/setup-chromedriver@2 12 | 13 | # Setup Flutter environment 14 | - uses: subosito/flutter-action@v2 15 | with: 16 | channel: "stable" 17 | 18 | # Download all the packages that the app uses 19 | - run: flutter pub get 20 | 21 | # Run all tests 22 | - working-directory: ./example_js_regex 23 | run: flutter drive --driver=test_driver/integration_test.dart --target=integration_test/duration/duration_parser_test.dart -d chrome 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # https://dart.dev/guides/libraries/private-files 4 | # Created by `dart pub` 5 | .dart_tool/ 6 | 7 | # Avoid committing pubspec.lock for library packages; see 8 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 9 | pubspec.lock 10 | 11 | # IntelliJ related 12 | *.iml 13 | *.ipr 14 | *.iws 15 | .idea/ -------------------------------------------------------------------------------- /.run/JS Regex.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.0 - Oct, 2023 2 | Placeholder version. MVP is under construction. 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Declarative, Inc. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Natural Language Processing (NLP) for Dart 2 | Natural language processing (NLP) identifies substrings within text, which represent a useful piece 3 | of information, and then associates the substring with that information. 4 | 5 | For example, NLP might receive text that says "I'll do it tomorrow". NLP identifies "tomorrow" as 6 | a meaningful term, and NLP is aware that "tomorrow" means the day after today. As a result, NLP 7 | associates "tomorrow" with a Dart `DateTime` that points to the day after today. 8 | 9 | NLP can be implemented for any such association. For example, NLP might be implemented to recognize 10 | that a number word, like "two", represents an `int`, like `2`. NLP is limited only by the data 11 | models and text recognizers, which are used to inspect the meaning of text. -------------------------------------------------------------------------------- /README_EXAMPLE.md: -------------------------------------------------------------------------------- 1 | # How recognizers work 2 | 3 | ## Operations 4 | ### Regex Composition 5 | Most regular expressions are built from other regular expressions in a composition process. 6 | 7 | When one regular expression is embedded in another, it creates the possibility that the composed 8 | regex contains multiple group names that are the same. But regex doesn't support repeated group 9 | names. Therefore, each regex pattern has its group names de-duplicated by adding suffixes to those 10 | names. 11 | 12 | Example: 13 | ```java 14 | // Regex for a duration followed by a duration unit 15 | public static final String DurationFollowedUnit = "(^\\s*{DurationUnitRegex}\\s+{SuffixAndRegex})|(^\\s*{SuffixAndRegex}?(\\s+|-)?{DurationUnitRegex})" 16 | .replace("{SuffixAndRegex}", SuffixAndRegex) 17 | .replace("{DurationUnitRegex}", DurationUnitRegex); 18 | 19 | // Regex for suffixes 20 | public static final String SuffixAndRegex = "(?\\s*(and)\\s+(an?\\s+)?(?half|quarter))"; 21 | 22 | // Regex for duration units 23 | public static final String DurationUnitRegex = "(?{DateUnitRegex}|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\\b" 24 | .replace("{DateUnitRegex}", DateUnitRegex); 25 | 26 | // Date unit regex 27 | public static final String DateUnitRegex = "(?(decade|year|(?month|week)|(?(business\\s+|week\\s*))?(?day)|fortnight|weekend)(?s)?|(?<=(^|\\s)\\d{1,4})[ymwd])\\b"; 28 | ``` 29 | 30 | Duration followed by a unit composed regex: 31 | ``` 32 | (^\s*(?(?(decade|year|(?month|week)|(?(business\s+|week\s*))?(?day)|fortnight|weekend)(?s)?|(?(^|\s)\d{1,4})[ymwd])\b|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\b\s+(?\s*(and)\s+(an?\s+)?(?half|quarter)))|(^\s*(?\s*(and)\s+(an?\s+)?(?half|quarter))?(\s+|-)?(?(?(decade|year|(?month|week)|(?(business\s+|week\s*))?(?day)|fortnight|weekend)(?s)?|(?(^|\s)\d{1,4})[ymwd])\b|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\b) 33 | ``` 34 | 35 | Notice that the fully composed regex contains multiple group names for "unit", "uoy", "suffix", etc. 36 | 37 | These duplicated group names are altered during regex composition so that every group name is 38 | unique. The group names have a separator appended ("iii") and then they have an incrementing number 39 | appended ("unitiii0", "unitiii1"). 40 | 41 | ### Extraction 42 | The extraction process identifies pieces of text that represent something of meaning, such as a 43 | number, duration, date, time, etc. These pieces of text are extracted from the input text, along 44 | with their position data within the input text. 45 | 46 | Given the source: `it happened when the baby was only ten months old` 47 | 48 | Using regex: 49 | ``` 50 | (^\s*(?(?(decade|year|(?month|week)|(?(business\s+|week\s*))?(?day)|fortnight|weekend)(?s)?|(?(^|\s)\d{1,4})[ymwd])\b|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\b\s+(?\s*(and)\s+(an?\s+)?(?half|quarter)))|(^\s*(?\s*(and)\s+(an?\s+)?(?half|quarter))?(\s+|-)?(?(?(decade|year|(?month|week)|(?(business\s+|week\s*))?(?day)|fortnight|weekend)(?s)?|(?(^|\s)\d{1,4})[ymwd])\b|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\b) 51 | ``` 52 | 53 | Duration Extraction: 54 | ``` 55 | Text: "ten months" 56 | Start: 35 57 | Length: 45 58 | ``` 59 | 60 | ### Re-ifying group names 61 | During regex composition, group names are altered for the purpose of de-duplication. However, the 62 | original group names are important - the group names represent the domain meaning of what they 63 | capture. For example "unitiii1" needs to recover "unit" so that the tokenization and parsing 64 | processes understand the meaning of the associated text. 65 | 66 | The Recognizers package implements `getMatches()` in `RegExpUtility` to recover the original group 67 | names and return matches with those names. 68 | 69 | ### Tokenization 70 | The extraction process produces `ExtractResult`s. These `ExtractResult`s are then converted into 71 | `Token`s. These tokens are then merged and otherwise processed. Then, the `Token`s are converted 72 | back into `ExtractResult`s and returned from the extraction process. 73 | 74 | ### Parsing 75 | Given a set of extractions, the parsing process attaches meaning to the extracted text. 76 | 77 | Given the source: `it happened when the baby was only ten months old` 78 | 79 | With Extraction: 80 | ``` 81 | Text: "ten months" 82 | Start: 35 83 | Length: 45 84 | ``` 85 | 86 | Parses the following: 87 | ``` 88 | text: ten months 89 | type: duration 90 | start: 35 91 | length: 10 92 | data: null 93 | timex: P10M 94 | resolution string: 95 | date time resolution result: 96 | - timex: P10M 97 | - mod: null 98 | - comment: null 99 | - past value: 2.592E7 100 | ``` 101 | 102 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | include: package:lints/recommended.yaml 15 | 16 | # Uncomment the following section to specify additional rules. 17 | 18 | # linter: 19 | # rules: 20 | # - camel_case_types 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** 25 | 26 | # For more information about the core and recommended set of lints, see 27 | # https://dart.dev/go/core-lints 28 | 29 | # For additional information about configuring this file, see 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | # Options configure how source code is generated for every 7 | # `@JsonSerializable`-annotated class in the package. 8 | # 9 | # The default value for each is listed. 10 | any_map: false 11 | checked: true 12 | constructor: "" 13 | create_factory: true 14 | create_field_map: false 15 | create_per_field_to_json: false 16 | create_to_json: false 17 | disallow_unrecognized_keys: true 18 | explicit_to_json: false 19 | field_rename: none 20 | generic_argument_factories: false 21 | ignore_unannotated: false 22 | include_if_null: true -------------------------------------------------------------------------------- /example/nlp_example.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names, constant_identifier_names 2 | 3 | import 'dart:convert'; 4 | 5 | import 'package:mason_logger/mason_logger.dart'; 6 | import 'package:nlp/nlp.dart'; 7 | 8 | final logger = Logger(); 9 | 10 | void main() { 11 | _printIntro(); 12 | 13 | do { 14 | final textToRecognize = logger.prompt("Enter the text to recognize:"); 15 | if (textToRecognize.trim().toLowerCase() == "exit") { 16 | logger.info("Thanks for trying the example app! Goodbye."); 17 | logger.info(""); 18 | return; 19 | } 20 | if (textToRecognize.trim().isEmpty) { 21 | logger.info("You didn't enter any text to recognize!"); 22 | logger.info(""); 23 | continue; 24 | } 25 | 26 | // Datetime recognizer This model will find any Date even if its write in colloquial language 27 | // E.g "I'll go back 8pm today" will return "2017-10-04 20:00:00" 28 | final results = DateTimeRecognizer.recognizeDateTime(textToRecognize); 29 | 30 | logger.info("I found the following entities (${results.length})"); 31 | for (final result in results) { 32 | logger.info("Type: ${result.typeName}"); 33 | JsonEncoder.withIndent(" ").convert({ 34 | "parentText": result.parentText, 35 | "text": result.text, 36 | "start": result.start, 37 | "end": result.end, 38 | "typeName": result.typeName, 39 | "resolution": result.resolution, 40 | }); 41 | } 42 | 43 | logger.info(""); 44 | } while (true); 45 | } 46 | 47 | void _printIntro() { 48 | logger.info("Welcome to the example app for the nlp package!"); 49 | logger.info(""); 50 | logger.info("This example app is based on the Microsoft Recognizers project."); 51 | logger.info(""); 52 | logger.info( 53 | "To try the recognizers enter a phrase and let us show you the different outputs for each recognizer or just type 'exit' to leave the application."); 54 | logger.info(""); 55 | logger.info("Here are some examples you could try:"); 56 | logger.info(""); 57 | logger.info("\"I want twenty meters of cable for tomorrow\""); 58 | logger.info("\"I'll be available tomorrow from 11am to 2pm to receive up to 5kg of sugar\""); 59 | logger.info("\"I'll be out between 4 and 22 this month\""); 60 | logger.info("\"I was the fifth person to finish the 10 km race\""); 61 | logger.info("\"The temperature this night will be of 40 deg celsius\""); 62 | logger 63 | .info("\"The american stock exchange said a seat was sold for down \$5,000 from the previous sale last friday\""); 64 | logger.info("\"It happened when the baby was only ten months old\""); 65 | logger.info(""); 66 | } 67 | -------------------------------------------------------------------------------- /example_js_regex/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /example_js_regex/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "92094802fecd9aa4c71492d6c0ea7e9eed82498e" 8 | channel: "master" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 92094802fecd9aa4c71492d6c0ea7e9eed82498e 17 | base_revision: 92094802fecd9aa4c71492d6c0ea7e9eed82498e 18 | - platform: web 19 | create_revision: 92094802fecd9aa4c71492d6c0ea7e9eed82498e 20 | base_revision: 92094802fecd9aa4c71492d6c0ea7e9eed82498e 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /example_js_regex/README.md: -------------------------------------------------------------------------------- 1 | # Flutter project to run RegEx with JS support 2 | 3 | Dart lacks support for reporting the indices of named capture groups, i.e., we can't get 4 | the location in the original string where a named capture group was found. But we need this 5 | RegEx behavior. 6 | 7 | JavaScript supports the reporting of indices for named capture groups. 8 | 9 | This Flutter project exists to bring JavaScript named group indices into Dart. This is a 10 | temporary approach because we'll eventually need cross-platform support. We'll prove our 11 | ability to implement the desired behavior using JS and then find some way to port that 12 | behavior to Dart for the final product. 13 | 14 | ## Integration Tests 15 | Because we depend upon JavaScript RegExp behavior, we can't run traditional Dart language 16 | tests, or even Flutter widget tests. We have to run web integration tests so that we can 17 | access JavaScript RegEx capabilities. 18 | 19 | To run the integration tests in this project, first start chromedriver: 20 | 21 | ```shell 22 | chromedriver --port=4444 23 | ``` 24 | 25 | Then, run the desired integration tests: 26 | 27 | ```shell 28 | flutter drive --driver=test_driver/integration_test.dart --target=integration_test/time/time_zone_extractor_test.dart -d chrome 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /example_js_regex/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Date > extractor >', () { 11 | for (final testCase in dateExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | testWidgets(input, (tester) async { 14 | final extractor = BaseDateExtractor( 15 | EnglishDateExtractorConfiguration( 16 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 17 | ), 18 | ); 19 | 20 | DateTime? referenceDate; 21 | 22 | final context = testCase["Context"] as Map?; 23 | 24 | if (context != null) { 25 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 26 | } 27 | 28 | final extractions = extractor.extractDateTime(input, referenceDate ?? DateTime.now()); 29 | 30 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 31 | final expectedResults = testCase["Results"] as List; 32 | 33 | expect(actualResults, expectedResults); 34 | }); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_parser_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Date > parser >', () { 11 | print('executing ${dateParserTestCases.length} test cases'); 12 | 13 | for (final testCase in dateParserTestCases) { 14 | final input = testCase["Input"] as String; 15 | testWidgets(input, (tester) async { 16 | final extractor = BaseDateExtractor( 17 | EnglishDateExtractorConfiguration( 18 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 19 | ), 20 | ); 21 | 22 | final parser = BaseDateParser( 23 | EnglishDateParserConfiguration( 24 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 25 | ), 26 | ); 27 | 28 | DateTime? referenceDate; 29 | 30 | final context = testCase["Context"] as Map?; 31 | 32 | if (context != null) { 33 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 34 | } 35 | 36 | referenceDate ??= DateTime.now(); 37 | 38 | final extractions = extractor.extractDateTime(input, referenceDate); 39 | 40 | final actualResults = 41 | extractions.map((extraction) => parser.parseDateTime(extraction, referenceDate!).toTestCaseJson()).toList(); 42 | final expectedResults = testCase["Results"] as List; 43 | 44 | expect(actualResults, expectedResults); 45 | }); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_period_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_period_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Date period > extractor >', () { 11 | for (final testCase in datePeriodExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | testWidgets(input, (tester) async { 14 | final extractor = BaseDatePeriodExtractor( 15 | EnglishDatePeriodExtractorConfiguration( 16 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 17 | ), 18 | ); 19 | 20 | DateTime? referenceDate; 21 | 22 | final context = testCase["Context"] as Map?; 23 | 24 | if (context != null) { 25 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 26 | } 27 | 28 | final extractions = extractor.extractDateTime(input, referenceDate ?? DateTime.now()); 29 | 30 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 31 | final expectedResults = testCase["Results"] as List; 32 | 33 | expect(actualResults, expectedResults); 34 | }); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_period_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_period_parser_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Date period > parser >', () { 11 | print('executing ${datePeriodParserTestCases.length} test cases'); 12 | 13 | for (final testCase in datePeriodParserTestCases) { 14 | final input = testCase["Input"] as String; 15 | testWidgets(input, (tester) async { 16 | final extractor = BaseDatePeriodExtractor( 17 | EnglishDatePeriodExtractorConfiguration( 18 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 19 | ), 20 | ); 21 | 22 | final parser = BaseDatePeriodParser( 23 | EnglishDatePeriodParserConfiguration( 24 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 25 | ), 26 | ); 27 | 28 | DateTime? referenceDate; 29 | 30 | final context = testCase["Context"] as Map?; 31 | 32 | if (context != null) { 33 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 34 | } 35 | 36 | referenceDate ??= DateTime.now(); 37 | 38 | final extractions = extractor.extractDateTime(input, referenceDate); 39 | 40 | final actualResults = 41 | extractions.map((extraction) => parser.parseDateTime(extraction, referenceDate!).toTestCaseJson()).toList(); 42 | final expectedResults = testCase["Results"] as List; 43 | 44 | expect(actualResults, expectedResults); 45 | }); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | void main() { 6 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 7 | 8 | group('Recognition > date >', () { 9 | testWidgets('04th Jan 2019', (widgetTester) async { 10 | final matches = DateTimeRecognizer.recognizeDateTime( 11 | "I'll go back 04th Jan 2019", 12 | ); 13 | 14 | expect(matches.length, 1); 15 | expect( 16 | matches.first.toJson(), 17 | { 18 | "parentText": null, 19 | "text": "04th jan 2019", 20 | "start": 13, 21 | "end": 25, 22 | "typeName": "datetimeV2.date", 23 | "resolution": { 24 | "values": [ 25 | { 26 | "timex": "2019-01-04", 27 | "type": "date", 28 | "value": "2019-01-04", 29 | }, 30 | ], 31 | }, 32 | }, 33 | ); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_time_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_time_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('DateTime > extractor >', () { 11 | for (final testCase in dateTimeExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | testWidgets(input, (tester) async { 14 | final extractor = BaseDateTimeExtractor( 15 | EnglishDateTimeExtractorConfiguration( 16 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 17 | ), 18 | ); 19 | 20 | DateTime? referenceDate; 21 | 22 | final context = testCase["Context"] as Map?; 23 | 24 | if (context != null) { 25 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 26 | } 27 | 28 | final extractions = extractor.extractDateTime(input, referenceDate ?? DateTime.now()); 29 | 30 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 31 | final expectedResults = testCase["Results"] as List; 32 | 33 | expect(actualResults, expectedResults); 34 | }); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_time_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_time_parser_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Datetime > parser >', () { 11 | print('executing ${dateTimeParserTestCases.length} test cases'); 12 | 13 | for (final testCase in dateTimeParserTestCases) { 14 | final input = testCase["Input"] as String; 15 | testWidgets(input, (tester) async { 16 | final extractor = BaseDateTimeExtractor( 17 | EnglishDateTimeExtractorConfiguration( 18 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 19 | ), 20 | ); 21 | 22 | final parser = BaseDateTimeParser( 23 | EnglishDateTimeParserConfiguration( 24 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 25 | ), 26 | ); 27 | 28 | DateTime? referenceDate; 29 | 30 | final context = testCase["Context"] as Map?; 31 | 32 | if (context != null) { 33 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 34 | } 35 | 36 | referenceDate ??= DateTime.now(); 37 | 38 | final extractions = extractor.extractDateTime(input, referenceDate); 39 | 40 | final actualResults = 41 | extractions.map((extraction) => parser.parseDateTime(extraction, referenceDate!).toTestCaseJson()).toList(); 42 | final expectedResults = testCase["Results"] as List; 43 | 44 | expect(actualResults, expectedResults); 45 | }); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_time_period_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_time_period_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Datetime period > extractor >', () { 11 | for (final testCase in dateTimePeriodExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | testWidgets(input, (tester) async { 14 | final extractor = BaseDateTimePeriodExtractor( 15 | EnglishDateTimePeriodExtractorConfiguration( 16 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 17 | ), 18 | ); 19 | 20 | DateTime? referenceDate; 21 | 22 | final context = testCase["Context"] as Map?; 23 | 24 | if (context != null) { 25 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 26 | } 27 | 28 | final extractions = extractor.extractDateTime(input, referenceDate ?? DateTime.now()); 29 | 30 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 31 | final expectedResults = testCase["Results"] as List; 32 | 33 | expect(actualResults, expectedResults); 34 | }); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/date_time_period_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'date_time_period_parser_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Datetime period > parser >', () { 11 | print('executing ${dateTimePeriodParserTestCases.length} test cases'); 12 | 13 | for (final testCase in dateTimePeriodParserTestCases) { 14 | final input = testCase["Input"] as String; 15 | testWidgets(input, (tester) async { 16 | final extractor = BaseDateTimePeriodExtractor( 17 | EnglishDateTimePeriodExtractorConfiguration( 18 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 19 | ), 20 | ); 21 | 22 | final parser = BaseDateTimePeriodParser( 23 | EnglishDateTimePeriodParserConfiguration( 24 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 25 | ), 26 | ); 27 | 28 | DateTime? referenceDate; 29 | 30 | final context = testCase["Context"] as Map?; 31 | 32 | if (context != null) { 33 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 34 | } 35 | 36 | referenceDate ??= DateTime.now(); 37 | 38 | final extractions = extractor.extractDateTime(input, referenceDate); 39 | 40 | final actualResults = 41 | extractions.map((extraction) => parser.parseDateTime(extraction, referenceDate!).toTestCaseJson()).toList(); 42 | final expectedResults = testCase["Results"] as List; 43 | 44 | expect(actualResults, expectedResults); 45 | }); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/set_extractor_cases.dart: -------------------------------------------------------------------------------- 1 | const setExtractorTestCases = [ 2 | { 3 | "Input": "I'll leave weekly", 4 | "Results": [ 5 | {"Text": "weekly", "Type": "set", "Start": 11, "Length": 6} 6 | ] 7 | }, 8 | { 9 | "Input": "I'll leave daily", 10 | "Results": [ 11 | {"Text": "daily", "Type": "set", "Start": 11, "Length": 5} 12 | ] 13 | }, 14 | { 15 | "Input": "I'll leave every day", 16 | "Results": [ 17 | {"Text": "every day", "Type": "set", "Start": 11, "Length": 9} 18 | ] 19 | }, 20 | { 21 | "Input": "I'll leave each month", 22 | "Results": [ 23 | {"Text": "each month", "Type": "set", "Start": 11, "Length": 10} 24 | ] 25 | }, 26 | { 27 | "Input": "I'll leave annually", 28 | "Results": [ 29 | {"Text": "annually", "Type": "set", "Start": 11, "Length": 8} 30 | ] 31 | }, 32 | { 33 | "Input": "I'll leave annual", 34 | "Results": [ 35 | {"Text": "annual", "Type": "set", "Start": 11, "Length": 6} 36 | ] 37 | }, 38 | { 39 | "Input": "I'll leave each two days", 40 | "Results": [ 41 | {"Text": "each two days", "Type": "set", "Start": 11, "Length": 13} 42 | ] 43 | }, 44 | { 45 | "Input": "I'll leave every three week", 46 | "Results": [ 47 | {"Text": "every three week", "Type": "set", "Start": 11, "Length": 16} 48 | ] 49 | }, 50 | { 51 | "Input": "I'll leave 3pm every day", 52 | "Results": [ 53 | {"Text": "3pm every day", "Type": "set", "Start": 11, "Length": 13} 54 | ] 55 | }, 56 | { 57 | "Input": "I'll leave 3pm each day", 58 | "Results": [ 59 | {"Text": "3pm each day", "Type": "set", "Start": 11, "Length": 12} 60 | ] 61 | }, 62 | { 63 | "Input": "I'll leave each 4/15", 64 | "Results": [ 65 | {"Text": "each 4/15", "Type": "set", "Start": 11, "Length": 9} 66 | ] 67 | }, 68 | { 69 | "Input": "I'll leave every monday", 70 | "Results": [ 71 | {"Text": "every monday", "Type": "set", "Start": 11, "Length": 12} 72 | ] 73 | }, 74 | { 75 | "Input": "I'll leave each monday 4pm", 76 | "Results": [ 77 | {"Text": "each monday 4pm", "Type": "set", "Start": 11, "Length": 15} 78 | ] 79 | }, 80 | { 81 | "Input": "I'll leave every morning", 82 | "Results": [ 83 | {"Text": "every morning", "Type": "set", "Start": 11, "Length": 13} 84 | ] 85 | }, 86 | { 87 | "Input": "I'll leave every morning at 9am", 88 | "Results": [ 89 | {"Text": "every morning at 9am", "Type": "set", "Start": 11, "Length": 20} 90 | ] 91 | }, 92 | { 93 | "Input": "I'll leave every afternoon at 4pm", 94 | "Results": [ 95 | {"Text": "every afternoon at 4pm", "Type": "set", "Start": 11, "Length": 22} 96 | ] 97 | }, 98 | { 99 | "Input": "I'll leave every night at 9pm", 100 | "Results": [ 101 | {"Text": "every night at 9pm", "Type": "set", "Start": 11, "Length": 18} 102 | ] 103 | }, 104 | { 105 | "Input": "I'll leave every night at 9", 106 | "Results": [ 107 | {"Text": "every night at 9", "Type": "set", "Start": 11, "Length": 16} 108 | ] 109 | }, 110 | { 111 | "Input": "I'll leave mornings at 9am", 112 | "Results": [ 113 | {"Text": "mornings at 9am", "Type": "set", "Start": 11, "Length": 15} 114 | ] 115 | }, 116 | { 117 | "Input": "I'll leave on mornings at 9", 118 | "Results": [ 119 | {"Text": "on mornings at 9", "Type": "set", "Start": 11, "Length": 16} 120 | ] 121 | }, 122 | { 123 | "Input": "I'll leave at 9am every Sunday", 124 | "Results": [ 125 | {"Text": "9am every sunday", "Type": "set", "Start": 14, "Length": 16} 126 | ] 127 | }, 128 | { 129 | "Input": "I'll leave at 9am on mondays", 130 | "Results": [ 131 | {"Text": "9am on mondays", "Type": "set", "Start": 14, "Length": 14} 132 | ] 133 | }, 134 | { 135 | "Input": "I'll leave at 9am mondays", 136 | "Results": [ 137 | {"Text": "9am mondays", "Type": "set", "Start": 14, "Length": 11} 138 | ] 139 | }, 140 | { 141 | "Input": "I'll leave on mondays", 142 | "Results": [ 143 | {"Text": "on mondays", "Type": "set", "Start": 11, "Length": 10} 144 | ] 145 | }, 146 | { 147 | "Input": "I'll leave on sundays", 148 | "Results": [ 149 | {"Text": "on sundays", "Type": "set", "Start": 11, "Length": 10} 150 | ] 151 | }, 152 | { 153 | "Input": "I'll leave sundays", 154 | "Results": [ 155 | {"Text": "sundays", "Type": "set", "Start": 11, "Length": 7} 156 | ] 157 | }, 158 | { 159 | "Input": "Can I do a booking for the 09th of May for 2 nights?", 160 | "Results": [ 161 | {"Text": "nights", "Type": "set", "Start": 45, "Length": 6} 162 | ] 163 | }, 164 | { 165 | "Input": "Let's meet once a week", 166 | "Context": {"ReferenceDateTime": "2016-11-07T00:00:00"}, 167 | "Results": [ 168 | {"Text": "once a week", "Type": "set", "Start": 11, "Length": 11} 169 | ] 170 | }, 171 | { 172 | "Input": "I go on vacation once a year", 173 | "Context": {"ReferenceDateTime": "2016-11-07T00:00:00"}, 174 | "Results": [ 175 | {"Text": "once a year", "Type": "set", "Start": 17, "Length": 11} 176 | ] 177 | }, 178 | { 179 | "Input": "Every other friday", 180 | "Results": [ 181 | {"Text": "every other friday", "Type": "set", "Start": 0, "Length": 18} 182 | ] 183 | }, 184 | { 185 | "Input": "Let's have a quarterly meeting.", 186 | "Results": [ 187 | {"Text": "quarterly", "Type": "set", "Start": 13, "Length": 9} 188 | ] 189 | } 190 | ]; 191 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/set_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'set_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Set > extractor >', () { 11 | for (final testCase in setExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | testWidgets(input, (tester) async { 14 | final extractor = BaseSetExtractor( 15 | EnglishSetExtractorConfiguration( 16 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 17 | ), 18 | ); 19 | 20 | DateTime? referenceDate; 21 | 22 | final context = testCase["Context"] as Map?; 23 | 24 | if (context != null) { 25 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 26 | } 27 | 28 | final extractions = extractor.extractDateTime(input, referenceDate ?? DateTime.now()); 29 | 30 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 31 | final expectedResults = testCase["Results"] as List; 32 | 33 | expect(actualResults, expectedResults); 34 | }); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/datetime/set_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'set_parser_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Datetime > parser >', () { 11 | print('executing ${setParserTestCases.length} test cases'); 12 | 13 | for (final testCase in setParserTestCases) { 14 | final input = testCase["Input"] as String; 15 | testWidgets(input, (tester) async { 16 | final extractor = BaseSetExtractor( 17 | EnglishSetExtractorConfiguration( 18 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 19 | ), 20 | ); 21 | 22 | final parser = BaseSetParser( 23 | EnglishSetParserConfiguration( 24 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 25 | ), 26 | ); 27 | 28 | DateTime? referenceDate; 29 | 30 | final context = testCase["Context"] as Map?; 31 | 32 | if (context != null) { 33 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 34 | } 35 | 36 | referenceDate ??= DateTime.now(); 37 | 38 | final extractions = extractor.extractDateTime(input, referenceDate); 39 | 40 | final actualResults = 41 | extractions.map((extraction) => parser.parseDateTime(extraction, referenceDate!).toTestCaseJson()).toList(); 42 | final expectedResults = testCase["Results"] as List; 43 | 44 | expect(actualResults, expectedResults); 45 | }); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/duration/duration_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'duration_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Duration > extractor >', () { 11 | for (final testCase in durationExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | 14 | testWidgets(input, (widgetTester) async { 15 | final extractions = DurationExtractor(config: EnglishDurationExtractorConfiguration()).extract(input); 16 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 17 | final expectedResults = testCase["Results"] as List; 18 | 19 | expect(actualResults, expectedResults); 20 | }); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/duration/duration_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:intl/intl.dart'; 4 | import 'package:nlp/nlp.dart'; 5 | 6 | import 'duration_parser_cases.dart'; 7 | 8 | void main() { 9 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | final dateTimeFormat = DateFormat("yyyy-MM-ddThh:mm:ss"); 12 | 13 | group('Duration > parser >', () { 14 | for (final testCase in durationParserTestCases) { 15 | final input = testCase["Input"] as String; 16 | 17 | final referenceDateTimeString = (testCase["Context"] as Map?)?["ReferenceDateTime"]; 18 | final referenceDateTime = 19 | referenceDateTimeString != null ? dateTimeFormat.parse(referenceDateTimeString) : DateTime.now(); 20 | 21 | testWidgets(input, (widgetTester) async { 22 | final extractions = DurationExtractor(config: EnglishDurationExtractorConfiguration()).extract(input); 23 | 24 | final parser = BaseDurationParser( 25 | EnglishDurationParserConfiguration( 26 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 27 | ), 28 | ); 29 | final parseResults = []; 30 | for (final extraction in extractions) { 31 | parseResults.add(parser.parseDateTime(extraction, referenceDateTime)); 32 | } 33 | 34 | final actualResults = parseResults.map((parseResult) => parseResult.toTestCaseJson()).toList(); 35 | final expectedResults = testCase["Results"] as List; 36 | 37 | expect(actualResults, expectedResults); 38 | }); 39 | } 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/infrastructure_test/js_recognition_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | void main() { 6 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 7 | 8 | group('JS regular expressions >', () { 9 | testWidgets('reports capture group names', (widgetTester) async { 10 | final dartResult = JsRegExp( 11 | '^(?\\d+),(?.+)\$', 12 | d: true, 13 | ).exec("1560979912,Caroline"); 14 | 15 | expect(dartResult!.matchingGroupNames, ['timestamp', 'author']); 16 | }); 17 | 18 | testWidgets('reports capture group indices', (widgetTester) async { 19 | final dartResult = JsRegExp( 20 | '^(?\\d+),(?.+)\$', 21 | d: true, 22 | ).exec("1560979912,Caroline"); 23 | 24 | expect(dartResult!.groups['timestamp'], '1560979912'); 25 | expect(dartResult.groups['author'], 'Caroline'); 26 | expect(dartResult.indices, [(0, 19), (0, 10), (11, 19)]); 27 | }); 28 | 29 | testWidgets('reports capture group indices by name', (widgetTester) async { 30 | final dartResult = JsRegExp( 31 | '^(?\\d+),(?.+)\$', 32 | d: true, 33 | ).exec("1560979912,Caroline"); 34 | 35 | expect(dartResult!.getGroupBounds('timestamp'), (0, 10)); 36 | expect(dartResult.getGroupBounds('author'), (11, 19)); 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/time/time_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | 4 | import 'time_extractor_cases.dart'; 5 | 6 | void main() { 7 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 8 | 9 | group('Time > extractor >', () { 10 | for (final testCase in timeExtractorTestCases) { 11 | final input = testCase["Input"] as String; 12 | 13 | // TODO: 14 | // testWidgets(input, (widgetTester) async { 15 | // final extractions = TimeExtractor(config: EnglishDurationExtractorConfiguration()).extract(input); 16 | // final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 17 | // final expectedResults = testCase["Results"] as List; 18 | // 19 | // expect(actualResults, expectedResults); 20 | // }); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/time/time_period_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'time_period_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Time period > extractor >', () { 11 | for (final testCase in timePeriodExtractorTestCases) { 12 | final input = testCase["Input"] as String; 13 | testWidgets(input, (tester) async { 14 | final extractor = BaseTimePeriodExtractor( 15 | EnglishTimePeriodExtractorConfiguration( 16 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 17 | ), 18 | ); 19 | 20 | DateTime? referenceDate; 21 | 22 | final context = testCase["Context"] as Map?; 23 | 24 | if (context != null) { 25 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 26 | } 27 | 28 | final extractions = extractor.extractDateTime(input, referenceDate ?? DateTime.now()); 29 | 30 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 31 | final expectedResults = testCase["Results"] as List; 32 | 33 | expect(actualResults, expectedResults); 34 | }); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/time/time_period_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'time_period_parser_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Time period > parser >', () { 11 | print('executing ${timePeriodParserTestCases.length} test cases'); 12 | 13 | for (final testCase in timePeriodParserTestCases) { 14 | final input = testCase["Input"] as String; 15 | testWidgets(input, (tester) async { 16 | final extractor = BaseTimePeriodExtractor( 17 | EnglishTimePeriodExtractorConfiguration( 18 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 19 | ), 20 | ); 21 | 22 | final parser = BaseTimePeriodParser( 23 | EnglishTimePeriodParserConfiguration( 24 | EnglishCommonDateTimeParserConfiguration(DateTimeOptions.None), 25 | ), 26 | ); 27 | 28 | DateTime? referenceDate; 29 | 30 | final context = testCase["Context"] as Map?; 31 | 32 | if (context != null) { 33 | referenceDate = DateTime.parse(context["ReferenceDateTime"]); 34 | } 35 | 36 | referenceDate ??= DateTime.now(); 37 | 38 | final extractions = extractor.extractDateTime(input, referenceDate); 39 | 40 | final actualResults = 41 | extractions.map((extraction) => parser.parseDateTime(extraction, referenceDate!).toTestCaseJson()).toList(); 42 | final expectedResults = testCase["Results"] as List; 43 | 44 | expect(actualResults, expectedResults); 45 | }); 46 | } 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/time/time_zone_extractor_cases.dart: -------------------------------------------------------------------------------- 1 | final timeZoneExtractorTestCases = [ 2 | { 3 | "Input": "Book me a room at beijing time", 4 | "NotSupported": "javascript", 5 | "Results": [ 6 | {"Text": "beijing time", "Type": "timezone", "Start": 18, "Length": 12} 7 | ] 8 | }, 9 | { 10 | "Input": "Book me a room at utc4:30", 11 | "NotSupported": "javascript", 12 | "Results": [ 13 | {"Text": "utc4:30", "Type": "timezone", "Start": 18, "Length": 7} 14 | ] 15 | }, 16 | { 17 | "Input": "Book me a room at gmt-3", 18 | "NotSupported": "javascript", 19 | "Results": [ 20 | {"Text": "gmt-3", "Type": "timezone", "Start": 18, "Length": 5} 21 | ] 22 | }, 23 | { 24 | "Input": "Book me a room at afghanistan standard time", 25 | "NotSupported": "javascript", 26 | "Results": [ 27 | {"Text": "afghanistan standard time", "Type": "timezone", "Start": 18, "Length": 25} 28 | ] 29 | }, 30 | { 31 | "Input": "Book me a room at aft", 32 | "NotSupported": "javascript", 33 | "Results": [ 34 | {"Text": "aft", "Type": "timezone", "Start": 18, "Length": 3} 35 | ] 36 | }, 37 | { 38 | "Input": "Book me a room at beijing-time", 39 | "NotSupported": "javascript", 40 | "Results": [ 41 | {"Text": "beijing-time", "Type": "timezone", "Start": 18, "Length": 12} 42 | ] 43 | }, 44 | { 45 | "Input": "Book me a room at St. Louis-time", 46 | "NotSupported": "javascript", 47 | "Results": [ 48 | {"Text": "St. Louis-time", "Type": "timezone", "Start": 18, "Length": 14} 49 | ] 50 | }, 51 | { 52 | "Input": "Book me a room at San José Time", 53 | "NotSupported": "javascript", 54 | "Results": [ 55 | {"Text": "San José Time", "Type": "timezone", "Start": 18, "Length": 13} 56 | ] 57 | }, 58 | { 59 | "Input": "For me, Christchurch Time, Colchester-time or Edinburgh time is OK.", 60 | "NotSupported": "javascript", 61 | "Results": [ 62 | {"Text": "Christchurch Time", "Type": "timezone", "Start": 8, "Length": 17}, 63 | {"Text": "Colchester-time", "Type": "timezone", "Start": 27, "Length": 15}, 64 | {"Text": "Edinburgh time", "Type": "timezone", "Start": 46, "Length": 14} 65 | ] 66 | }, 67 | { 68 | "Input": "Cortana will email you to find a time which works in the Sydney timezone.", 69 | "NotSupported": "javascript", 70 | "Results": [ 71 | {"Text": "Sydney timezone", "Type": "timezone", "Start": 57, "Length": 15} 72 | ] 73 | }, 74 | { 75 | "Input": "Cortana will email you to find a time which works in the Montréal time.", 76 | "NotSupported": "javascript", 77 | "Results": [ 78 | {"Text": "Montréal time", "Type": "timezone", "Start": 57, "Length": 13} 79 | ] 80 | }, 81 | { 82 | "Input": "Cortana will email you to find a time which works in the Montreal time.", 83 | "NotSupported": "javascript", 84 | "Results": [ 85 | {"Text": "Montreal time", "Type": "timezone", "Start": 57, "Length": 13} 86 | ] 87 | }, 88 | { 89 | "Input": "Book me a room at pt", 90 | "NotSupported": "javascript", 91 | "Results": [ 92 | {"Text": "pt", "Type": "timezone", "Start": 18, "Length": 2} 93 | ] 94 | }, 95 | { 96 | "Input": "Book me a room at et", 97 | "NotSupported": "javascript", 98 | "Results": [ 99 | {"Text": "et", "Type": "timezone", "Start": 18, "Length": 2} 100 | ] 101 | }, 102 | { 103 | "Input": "let's meet Saint Barthélemy time", 104 | "NotSupported": "javascript", 105 | "Results": [ 106 | {"Text": "Saint Barthélemy time", "Type": "timezone", "Start": 11, "Length": 21} 107 | ] 108 | }, 109 | { 110 | "Input": "let's meet saint barthelemy timezone", 111 | "NotSupported": "javascript", 112 | "Results": [ 113 | {"Text": "saint barthelemy timezone", "Type": "timezone", "Start": 11, "Length": 25} 114 | ] 115 | }, 116 | { 117 | "Input": "It is the outcome of the vote that counts.", 118 | "Comment": "This case is to verify that the utc substring won't be extracted from the word outcome.", 119 | "NotSupported": "javascript", 120 | "Results": [] 121 | }, 122 | { 123 | "Input": "Show me times at Lincoln Square", 124 | "Comment": "This case is to verify that the 'me time' substring won't be extracted from the 'me times'.", 125 | "NotSupported": "javascript", 126 | "Results": [] 127 | }, 128 | { 129 | "Input": "I said New York time, not York time", 130 | "Comment": "Extract longest item when there are some overlap items", 131 | "NotSupported": "javascript", 132 | "Results": [ 133 | {"Text": "New York time", "Type": "timezone", "Start": 7, "Length": 13}, 134 | {"Text": "York time", "Type": "timezone", "Start": 26, "Length": 9} 135 | ] 136 | }, 137 | { 138 | "Input": "I'm in the pacific timezone", 139 | "NotSupported": "javascript", 140 | "Results": [ 141 | {"Text": "pacific timezone", "Type": "timezone", "Start": 11, "Length": 16} 142 | ] 143 | }, 144 | { 145 | "Input": "Let's meet at 1pm mountain timezone", 146 | "NotSupported": "javascript", 147 | "Results": [ 148 | {"Text": "mountain timezone", "Type": "timezone", "Start": 18, "Length": 17} 149 | ] 150 | }, 151 | { 152 | "Input": "It's 1pm Eastern Daylight Time", 153 | "NotSupported": "javascript", 154 | "Results": [ 155 | {"Text": "eastern daylight time", "Type": "timezone", "Start": 9, "Length": 21} 156 | ] 157 | }, 158 | { 159 | "Input": "It's about 1pm ACDT", 160 | "NotSupported": "javascript", 161 | "Results": [ 162 | {"Text": "acdt", "Type": "timezone", "Start": 15, "Length": 4} 163 | ] 164 | }, 165 | {"Input": "Once upon a time...", "NotSupported": "javascript", "Results": []} 166 | ]; 167 | -------------------------------------------------------------------------------- /example_js_regex/integration_test/time/time_zone_extractor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:nlp/nlp.dart'; 4 | 5 | import 'time_zone_extractor_cases.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | group('Time zone > extractor >', () { 11 | for (final testCase in timeZoneExtractorTestCases.sublist(0, 1)) { 12 | final input = testCase["Input"] as String; 13 | 14 | testWidgets(input, (widgetTester) async { 15 | final extractions = BaseTimeZoneExtractor(EnglishTimeZoneExtractorConfiguration()).extract(input); 16 | final actualResults = extractions.map((extraction) => extraction.toTestCaseJson()).toList(); 17 | final expectedResults = testCase["Results"] as List; 18 | 19 | expect(actualResults, expectedResults); 20 | }); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /example_js_regex/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example_js_regex 2 | description: "A new Flutter project." 3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: '>=3.0.0 <4.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | nlp: 15 | path: ../ 16 | 17 | collection: ^1.18.0 18 | intl: ^0.19.0 19 | super_editor: 20 | git: 21 | url: https://github.com/superlistapp/super_editor 22 | path: super_editor 23 | ref: stable 24 | 25 | dependency_overrides: 26 | super_text_layout: 27 | git: 28 | url: https://github.com/superlistapp/super_editor 29 | path: super_text_layout 30 | ref: stable 31 | attributed_text: 32 | git: 33 | url: https://github.com/superlistapp/super_editor 34 | path: attributed_text 35 | ref: stable 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | integration_test: 41 | sdk: flutter 42 | 43 | flutter_lints: ^3.0.0 44 | 45 | flutter: 46 | uses-material-design: true 47 | 48 | # To add assets to your application, add an assets section, like this: 49 | # assets: 50 | # - images/a_dot_burr.jpeg 51 | # - images/a_dot_ham.jpeg 52 | 53 | # An image asset can refer to one or more resolution-specific "variants", see 54 | # https://flutter.dev/assets-and-images/#resolution-aware 55 | 56 | # For details regarding adding assets from package dependencies, see 57 | # https://flutter.dev/assets-and-images/#from-packages 58 | 59 | # To add custom fonts to your application, add a fonts section here, 60 | # in this "flutter" section. Each entry in this list should have a 61 | # "family" key with the font family name, and a "fonts" key with a 62 | # list giving the asset and other descriptors for the font. For 63 | # example: 64 | # fonts: 65 | # - family: Schyler 66 | # fonts: 67 | # - asset: fonts/Schyler-Regular.ttf 68 | # - asset: fonts/Schyler-Italic.ttf 69 | # style: italic 70 | # - family: Trajan Pro 71 | # fonts: 72 | # - asset: fonts/TrajanPro.ttf 73 | # - asset: fonts/TrajanPro_Bold.ttf 74 | # weight: 700 75 | # 76 | # For details regarding fonts from package dependencies, 77 | # see https://flutter.dev/custom-fonts/#from-packages 78 | -------------------------------------------------------------------------------- /example_js_regex/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver_extended.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /example_js_regex/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Bounty-Hunters/nlp/d28f812c0ee1d67ce900b9ebe399f58d11418bd6/example_js_regex/web/favicon.png -------------------------------------------------------------------------------- /example_js_regex/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Bounty-Hunters/nlp/d28f812c0ee1d67ce900b9ebe399f58d11418bd6/example_js_regex/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example_js_regex/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Bounty-Hunters/nlp/d28f812c0ee1d67ce900b9ebe399f58d11418bd6/example_js_regex/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example_js_regex/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Bounty-Hunters/nlp/d28f812c0ee1d67ce900b9ebe399f58d11418bd6/example_js_regex/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example_js_regex/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutter-Bounty-Hunters/nlp/d28f812c0ee1d67ce900b9ebe399f58d11418bd6/example_js_regex/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example_js_regex/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example_js_regex 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example_js_regex/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example_js_regex", 3 | "short_name": "example_js_regex", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/nlp.dart: -------------------------------------------------------------------------------- 1 | library; 2 | 3 | // NLP Domain 4 | export 'src/date_time/constants.dart'; 5 | export 'src/date_time/english_date_time.dart'; 6 | export 'src/date_time/english_date_time_parser.dart'; 7 | export 'src/date_time/date_time_recognizer.dart'; 8 | export 'src/date_time/base_date_extractor.dart'; 9 | export 'src/date_time/english_date_extractor.dart'; 10 | export 'src/date_time/base_date_parser.dart'; 11 | export 'src/date_time/english/english_date_parser_configuration.dart'; 12 | export 'src/date_time/base_datetime_extractor.dart'; 13 | export 'src/date_time/english/english_date_time_extractor_configuration.dart'; 14 | export 'src/date_time/base_time_parser.dart'; 15 | export 'src/date_time/base_datetime_parser.dart'; 16 | export 'src/date_time/english/english_date_time_parser_configuration.dart'; 17 | export 'src/date_time/base_time_period_extractor.dart'; 18 | export 'src/date_time/english/english_time_period_extractor_configuration.dart'; 19 | export 'src/date_time/base_time_period_parser.dart'; 20 | export 'src/date_time/english/english_time_period_parser_configuration.dart'; 21 | 22 | export 'src/date_time/base_date_period_extractor.dart'; 23 | export 'src/date_time/english/english_date_period_extractor_configuration.dart'; 24 | 25 | export 'src/date_time/base_date_time_period_extractor.dart'; 26 | export 'src/date_time/english/english_date_time_period_extractor_configuration.dart'; 27 | export 'src/date_time/base_date_period_parser.dart'; 28 | export 'src/date_time/english/english_date_period_parser_configuration.dart'; 29 | 30 | export 'src/date_time/base_date_time_period_parser.dart'; 31 | export 'src/date_time/english/english_date_time_period_parser_configuration.dart'; 32 | 33 | export 'src/date_time/base_set_extractor.dart'; 34 | export 'src/date_time/english/english_set_extractor_configuration.dart'; 35 | 36 | export 'src/date_time/base_set_parser.dart'; 37 | export 'src/date_time/english/english_set_parser_configuration.dart'; 38 | 39 | export 'src/date_time/base_merged_date_time_extractor.dart'; 40 | export 'src/date_time/base_merged_date_time_parser.dart'; 41 | export 'src/core/english_merged_extractor.dart'; 42 | export 'src/core/global_recognizer.dart'; 43 | 44 | export 'src/duration/base_duration_parser.dart'; 45 | export 'src/duration/duration.dart'; 46 | export 'src/duration/duration_extractor.dart'; 47 | export 'src/duration/english_duration_parser_configuration.dart'; 48 | 49 | export 'src/numbers/numbers.dart'; 50 | 51 | export 'src/time/english_time_zone.dart'; 52 | export 'src/time/english_time_zone_extractor.dart'; 53 | export 'src/time/time_zone_extractor.dart'; 54 | 55 | // NLP Processing Primitives 56 | export 'src/core/extraction.dart'; 57 | export 'src/core/parser.dart'; 58 | 59 | // Infrastructure 60 | export 'src/regular_expressions/regular_expressions_extensions.dart'; 61 | 62 | // Custom RegExp 63 | export 'src/regular_expressions/js_regexp.dart'; 64 | -------------------------------------------------------------------------------- /lib/src/core/cultures.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | class Culture { 4 | static List getSupportedCultureCodes() { 5 | return SupportedCultures.map((c) => c.cultureCode).toList(); 6 | } 7 | 8 | static const SupportedCultures = { 9 | Culture("English", English), 10 | Culture("Chinese", Chinese), 11 | Culture("Spanish", Spanish), 12 | Culture("Portuguese", Portuguese), 13 | Culture("French", French), 14 | Culture("German", German), 15 | Culture("Japanese", Japanese), 16 | Culture("Dutch", Dutch), 17 | Culture("Italian", Italian), 18 | }; 19 | 20 | static const English = "en-us"; 21 | static const Chinese = "zh-cn"; 22 | static const Spanish = "es-es"; 23 | static const Portuguese = "pt-br"; 24 | static const French = "fr-fr"; 25 | static const German = "de-de"; 26 | static const Japanese = "ja-jp"; 27 | static const Dutch = "nl-nl"; 28 | static const Italian = "it-it"; 29 | 30 | const Culture(this.cultureName, this.cultureCode); 31 | 32 | final String cultureName; 33 | final String cultureCode; 34 | } 35 | 36 | class CultureInfo { 37 | static CultureInfo getCultureInfo(String cultureCode) { 38 | return CultureInfo(cultureCode); 39 | } 40 | 41 | const CultureInfo(this.cultureCode); 42 | 43 | final String cultureCode; 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/core/definition_loader.dart: -------------------------------------------------------------------------------- 1 | class DefinitionLoader { 2 | static Map LoadAmbiguityFilters(Map filters) { 3 | var ambiguityFiltersDict = {}; 4 | 5 | for (var item in filters.entries) { 6 | if ("null" != item.key) { 7 | ambiguityFiltersDict.putIfAbsent(RegExp(item.key), () => RegExp(item.value)); 8 | } 9 | } 10 | 11 | return ambiguityFiltersDict; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/core/extraction.dart: -------------------------------------------------------------------------------- 1 | abstract interface class IExtractor { 2 | List extract(String input); 3 | } 4 | 5 | class ExtractResult { 6 | ExtractResult({ 7 | required this.start, 8 | required this.length, 9 | required this.text, 10 | this.type, 11 | this.data, 12 | this.metadata, 13 | }); 14 | 15 | int start; 16 | int length; 17 | String? type; 18 | String text; 19 | Object? data; 20 | Metadata? metadata; 21 | 22 | bool isOverlap(ExtractResult er) { 23 | return _isOverlap(this, er); 24 | } 25 | 26 | _isOverlap(ExtractResult er1, ExtractResult er2) { 27 | return !(er1.start >= er2.start + er2.length) && !(er2.start >= er1.start + er1.length); 28 | } 29 | 30 | bool isCover(ExtractResult er) { 31 | return _isCover(this, er); 32 | } 33 | 34 | bool _isCover(ExtractResult er1, ExtractResult er2) { 35 | return ((er2.start < er1.start) && ((er2.start + er2.length) >= (er1.start + er1.length))) || 36 | ((er2.start <= er1.start) && ((er2.start + er2.length) > (er1.start + er1.length))); 37 | } 38 | 39 | /// Serialized this [ExtractResult] into a form that can be compared with JSON test cases. 40 | Map toTestCaseJson() { 41 | return { 42 | if (type != null) // 43 | "Type": type, 44 | "Text": text, 45 | "Start": start, 46 | "Length": length, 47 | // We don't include "data" or "metadata" because either the test cases don't want them, 48 | // or we're incorrectly setting these properties and they're triggering test failures. 49 | // Either way, for now, we don't include them. 50 | }; 51 | } 52 | } 53 | 54 | class Metadata { 55 | Metadata({ 56 | this.isDurationWithBeforeAndAfter = false, 57 | this.isHoliday = false, 58 | this.isMealtime = false, 59 | this.possiblyIncludePeriodEnd = false, 60 | this.isOrdinalRelative = false, 61 | this.hasMod = false, 62 | }); 63 | 64 | bool isHoliday; 65 | 66 | bool hasMod = false; 67 | 68 | String offset = ""; 69 | 70 | String relativeTo = ""; 71 | 72 | // For cases like "from 2014 to 2018", the period end "2018" could be inclusive or exclusive 73 | // 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 74 | bool isPossiblyIncludePeriodEnd = false; 75 | 76 | // For cases like "2015年以前" (usually regards as "before 2015" in English), "5天以前" 77 | // (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". 78 | // We use this flag to avoid duplicate judgment both in the Extraction step and Parse step. 79 | // Currently, this flag is only used in Chinese DateTime as other languages don't have this ambiguity cases. 80 | bool isDurationWithBeforeAndAfter; 81 | 82 | bool isDurationDateWithWeekday = false; 83 | 84 | bool isHolidayRange = false; 85 | 86 | bool isHolidayWeekend = false; 87 | 88 | String holidayName = ""; 89 | 90 | bool isOrdinalRelative; 91 | 92 | bool isMealtime = false; 93 | 94 | bool possiblyIncludePeriodEnd; 95 | 96 | bool isDurationWithAgoAndLater = false; 97 | } 98 | 99 | class Token { 100 | static List mergeAllTokens(List tokens, String text, String extractorName) { 101 | final result = []; 102 | final mergedTokens = []; 103 | 104 | tokens.sort((o1, o2) { 105 | if (o1.start != o2.start) { 106 | return o1.start - o2.start; 107 | } 108 | 109 | return o2.length - o1.length; 110 | }); 111 | 112 | for (final token in tokens) { 113 | bool bAdd = true; 114 | for (int i = 0; i < mergedTokens.length && bAdd; i++) { 115 | // It is included in one of the current tokens 116 | if (token.start >= mergedTokens[i].start && token.end <= mergedTokens[i].end) { 117 | bAdd = false; 118 | } 119 | 120 | // If it contains overlaps 121 | if (token.start > mergedTokens[i].start && token.start < mergedTokens[i].end) { 122 | bAdd = false; 123 | } 124 | 125 | // It includes one of the tokens and should replace the included one 126 | if (token.start <= mergedTokens[i].start && token.end >= mergedTokens[i].end) { 127 | bAdd = false; 128 | mergedTokens[i] = token; 129 | } 130 | } 131 | 132 | if (bAdd) { 133 | mergedTokens.add(token); 134 | } 135 | } 136 | 137 | for (final token in mergedTokens) { 138 | String substring = text.substring(token.start, token.end); 139 | 140 | ExtractResult er = ExtractResult( 141 | start: token.start, 142 | length: token.length, 143 | text: substring, 144 | type: extractorName, 145 | data: null, 146 | metadata: token.metadata, 147 | ); 148 | 149 | result.add(er); 150 | } 151 | 152 | return result; 153 | } 154 | 155 | static List getTokenFromRegex(RegExp pattern, String text) { 156 | final result = []; 157 | 158 | final matches = pattern.allMatches(text); 159 | for (final match in matches) { 160 | result.add( 161 | Token(match.start, match.end), 162 | ); 163 | } 164 | 165 | return result; 166 | } 167 | 168 | Token(this.start, this.end, [this.text, this.metadata]); 169 | 170 | final int start; 171 | final int end; 172 | int get length => end < start ? 0 : end - start; 173 | final String? text; 174 | final Metadata? metadata; 175 | } 176 | -------------------------------------------------------------------------------- /lib/src/core/global_recognizer.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/model_result.dart'; 2 | import 'package:nlp/src/date_time/date_time_recognizer.dart'; 3 | 4 | class GlobalRecognizer { 5 | static List recognize(String text) { 6 | final results = [ 7 | ...DateTimeRecognizer.recognizeDateTime(text), 8 | ]; 9 | 10 | return results; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/core/model_result.dart: -------------------------------------------------------------------------------- 1 | class ModelResult { 2 | static final String parentTextKey = "parentText"; 3 | 4 | const ModelResult({ 5 | required this.text, 6 | required this.start, 7 | required this.end, 8 | required this.typeName, 9 | required this.resolution, 10 | this.parentText, 11 | }); 12 | 13 | final String? parentText; 14 | final String text; 15 | final int start; 16 | final int end; 17 | final String typeName; 18 | final Map resolution; // Note: This is expected to be sorted 19 | 20 | ModelResult copyWith({ 21 | String? parentText, 22 | String? text, 23 | int? start, 24 | int? end, 25 | String? typeName, 26 | Map? resolution, 27 | }) { 28 | return ModelResult( 29 | parentText: parentText ?? this.parentText, 30 | text: text ?? this.text, 31 | start: start ?? this.start, 32 | end: end ?? this.end, 33 | typeName: typeName ?? this.typeName, 34 | resolution: resolution ?? this.resolution, 35 | ); 36 | } 37 | 38 | Map toJson() { 39 | return { 40 | "parentText": parentText, 41 | "text": text, 42 | "start": start, 43 | "end": end, 44 | "typeName": typeName, 45 | "resolution": resolution, 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/core/number_format_utility.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | 3 | import 'package:nlp/src/core/cultures.dart'; 4 | import 'package:nlp/src/numbers/number_constants.dart'; 5 | 6 | class NumberFormatUtility { 7 | static final _supportedCultures = { 8 | Culture.English: LongFormatType.DoubleNumCommaDot, 9 | Culture.Spanish: LongFormatType.DoubleNumDotComma, 10 | Culture.Portuguese: LongFormatType.DoubleNumDotComma, 11 | Culture.French: LongFormatType.DoubleNumDotComma, 12 | Culture.German: LongFormatType.DoubleNumDotComma, 13 | Culture.Chinese: null, 14 | Culture.Japanese: LongFormatType.DoubleNumDotComma, 15 | }; 16 | 17 | static String format(Object value, CultureInfo culture) { 18 | double doubleValue = value as double; 19 | String result; 20 | 21 | // EXPONENTIAL_AT: [-5, 15] }); 22 | // For small positive decimal places. E.g.: 0,000015 or 0,0000015 -> 1.5E-05 or 1.5E-06 23 | try { 24 | if (doubleValue > 0 && doubleValue != doubleValue.round() && doubleValue < 1E-4) { 25 | result = doubleValue.toString(); 26 | } else { 27 | // FIXME (Matt): I wasn't sure what the following code was trying to achieve so I 28 | // replaced it with a direct toString() on the double. 29 | // BigDecimal bc = BigDecimal(doubleValue, MathContext(15, RoundingMode.HALF_EVEN)); 30 | // result = bc.toString(); 31 | result = doubleValue.toString(); 32 | } 33 | } catch (exception) { 34 | return value.toString(); 35 | } 36 | 37 | result = result.replaceAll('e', 'E'); 38 | if (result.contains("E-")) { 39 | final parts = result.split(RegExp.escape("E-")); 40 | parts[0] = QueryProcessor.trimEnd(parts[0], ".0"); 41 | parts[1] = parts[1].padLeft(2, '0'); 42 | result = parts.join("E-"); 43 | } 44 | 45 | if (result.contains("E+")) { 46 | final parts = result.split(RegExp.escape("E+")); 47 | parts[0] = QueryProcessor.trimEnd(parts[0], "0"); 48 | result = parts.join("E+"); 49 | } 50 | 51 | if (result.contains(".")) { 52 | result = QueryProcessor.trimEnd(result, "0"); 53 | result = QueryProcessor.trimEnd(result, "."); 54 | } 55 | 56 | if (_supportedCultures.containsKey(culture.cultureCode)) { 57 | final longFormat = _supportedCultures[culture.cultureCode]; 58 | if (longFormat != null) { 59 | final buffer = StringBuffer(); 60 | for (int i = 0; i < result.length; i += 1) { 61 | buffer.write(_changeMark(result[i], longFormat)); 62 | } 63 | 64 | result = buffer.toString(); 65 | } 66 | } 67 | 68 | return result; 69 | } 70 | 71 | static String _changeMark(String c, LongFormatType longFormat) { 72 | if (c == '.') { 73 | return longFormat.decimalsMark; 74 | } else if (c == ',') { 75 | return longFormat.thousandsMark; 76 | } 77 | 78 | return c; 79 | } 80 | } 81 | 82 | class LongFormatType { 83 | // Reference Value : 1234567.89 84 | 85 | // 1,234,567 86 | static LongFormatType IntegerNumComma = LongFormatType(',', '\0'); 87 | 88 | // 1.234.567 89 | static LongFormatType IntegerNumDot = LongFormatType('.', '\0'); 90 | 91 | // 1 234 567 92 | static LongFormatType IntegerNumBlank = LongFormatType(' ', '\0'); 93 | 94 | // 1 234 567 95 | static LongFormatType IntegerNumNoBreakSpace = LongFormatType(Constants.NO_BREAK_SPACE, '\0'); 96 | 97 | // 1'234'567 98 | static LongFormatType IntegerNumQuote = LongFormatType('\'', '\0'); 99 | 100 | // 1,234,567.89 101 | static LongFormatType DoubleNumCommaDot = LongFormatType(',', '.'); 102 | 103 | // 1,234,567·89 104 | static LongFormatType DoubleNumCommaCdot = LongFormatType(',', '·'); 105 | 106 | // 1 234 567,89 107 | static LongFormatType DoubleNumBlankComma = LongFormatType(' ', ','); 108 | 109 | // 1 234 567,89 110 | static LongFormatType DoubleNumNoBreakSpaceComma = LongFormatType(Constants.NO_BREAK_SPACE, ','); 111 | 112 | // 1 234 567.89 113 | static LongFormatType DoubleNumBlankDot = LongFormatType(' ', '.'); 114 | 115 | // 1 234 567.89 116 | static LongFormatType DoubleNumNoBreakSpaceDot = LongFormatType(Constants.NO_BREAK_SPACE, '.'); 117 | 118 | // 1.234.567,89 119 | static LongFormatType DoubleNumDotComma = LongFormatType('.', ','); 120 | 121 | // 1'234'567,89 122 | static LongFormatType DoubleNumQuoteComma = LongFormatType('\'', ','); 123 | 124 | LongFormatType(this.thousandsMark, this.decimalsMark); 125 | 126 | final String decimalsMark; 127 | final String thousandsMark; 128 | } 129 | 130 | class QueryProcessor { 131 | static String trimEnd(String input, String chars) { 132 | return input.replaceAll("[${RegExp.escape(chars)}]+\$", ""); 133 | } 134 | 135 | static List split(String input, List delimiters) { 136 | final delimitersRegex = delimiters.map((s) => RegExp.escape(s)).join("|"); 137 | 138 | return input.split(delimitersRegex).where((s) => s.isNotEmpty).toList(); 139 | } 140 | 141 | static String? removeDiacritics(String? query) { 142 | if (query == null) { 143 | return null; 144 | } 145 | 146 | return removeDiacritics(query); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/src/core/parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/nlp.dart'; 2 | import 'package:nlp/src/core/extraction.dart'; 3 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 4 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 5 | 6 | abstract interface class IMergedParserConfiguration implements ICommonDateTimeParserConfiguration { 7 | RegExp getBeforeRegex(); 8 | 9 | RegExp getAfterRegex(); 10 | 11 | RegExp getSinceRegex(); 12 | 13 | RegExp getAroundRegex(); 14 | 15 | RegExp getSuffixAfterRegex(); 16 | 17 | RegExp getYearRegex(); 18 | 19 | RegExp getEqualRegex(); 20 | 21 | bool checkBothBeforeAfter(); 22 | 23 | // IDateTimeParser getGetParser(); 24 | // 25 | // IDateTimeParser getHolidayParser(); 26 | 27 | // IDateTimeParser getTimeZoneParser(); 28 | 29 | // TODO: bring back 30 | // StringMatcher getSuperfluousWordMatcher(); 31 | } 32 | 33 | abstract interface class IParser { 34 | ParseResult? parse(ExtractResult extractResult); 35 | } 36 | 37 | class ParseResult extends ExtractResult { 38 | factory ParseResult.fromExtractResult(ExtractResult extractResult) { 39 | return ParseResult( 40 | start: extractResult.start, 41 | length: extractResult.length, 42 | text: extractResult.text, 43 | type: extractResult.type, 44 | data: extractResult.data, 45 | metadata: extractResult.metadata, 46 | ); 47 | } 48 | 49 | ParseResult({ 50 | required super.start, 51 | required super.length, 52 | required super.text, 53 | super.type, 54 | super.data, 55 | this.value, 56 | this.resolutionStr, 57 | super.metadata, 58 | }); 59 | 60 | // Value is for resolution. 61 | // e.g. 1000 for "one thousand". 62 | // The resolutions are different for different parsers. 63 | // Therefore, we use object here. 64 | Object? value; 65 | 66 | // Output the value in string format. 67 | // It is used in some parsers. 68 | String? resolutionStr; 69 | 70 | /// Serialized this [ParseResult] into a form that can be compared with JSON test cases. 71 | @override 72 | Map toTestCaseJson() { 73 | return { 74 | ...super.toTestCaseJson(), 75 | if (value != null) // 76 | "Value": value is DateTimeResolutionResult ? (value as DateTimeResolutionResult).toTestCaseJson() : value, 77 | if (resolutionStr != null && resolutionStr!.isNotEmpty) // 78 | "resolutionStr": resolutionStr, 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/core/range_timex_component.dart: -------------------------------------------------------------------------------- 1 | class RangeTimexComponents { 2 | RangeTimexComponents({ 3 | this.beginTimex = '', 4 | this.endTimex = '', 5 | this.durationTimex = '', 6 | this.isValid = false, 7 | }); 8 | String beginTimex; 9 | 10 | String endTimex; 11 | 12 | String durationTimex; 13 | 14 | bool isValid; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/core/string_utility.dart: -------------------------------------------------------------------------------- 1 | class StringUtility { 2 | static bool isNullOrEmpty(String? source) { 3 | return source == null || source.isEmpty; 4 | } 5 | 6 | static bool isNullOrWhiteSpace(String? source) { 7 | return source == null || source.trim().isEmpty; 8 | } 9 | 10 | static bool isNotNullOrWhiteSpace(String? source) { 11 | return !isNullOrWhiteSpace(source); 12 | } 13 | 14 | static String format(double d) { 15 | if (d == d.toInt()) { 16 | return "${d.toInt()}"; 17 | } 18 | 19 | return d.toString(); 20 | } 21 | 22 | static bool isDigit( 23 | String? character, { 24 | bool positiveOnly = false, 25 | }) { 26 | if (character == null || character.isEmpty || character.length > 1) { 27 | return false; 28 | } 29 | final unicodeValue = character.codeUnitAt(0); 30 | return unicodeValue >= 48 && unicodeValue <= 57; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/core/task_mode_processing.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/constants.dart'; 2 | import 'package:nlp/src/date_time/time_of_day_resolution_result.dart'; 3 | 4 | class TasksModeProcessing { 5 | static TimeOfDayResolutionResult TasksModeResolveTimeOfDay(String tod) { 6 | var result = TimeOfDayResolutionResult(); 7 | switch (tod) { 8 | case DateTimeConstants.EarlyMorning: 9 | result.Timex = DateTimeConstants.EarlyMorning; 10 | result.BeginHour = TasksModeConstants.EarlyMorningBeginHour; 11 | result.EndHour = TasksModeConstants.EarlyMorningEndHour; 12 | break; 13 | case DateTimeConstants.Morning: 14 | result.Timex = DateTimeConstants.Morning; 15 | result.BeginHour = TasksModeConstants.MorningBeginHour; 16 | result.EndHour = TasksModeConstants.MorningEndHour; 17 | break; 18 | case DateTimeConstants.MidDay: 19 | result.Timex = DateTimeConstants.MidDay; 20 | result.BeginHour = TasksModeConstants.MidDayBeginHour; 21 | result.EndHour = TasksModeConstants.MidDayEndHour; 22 | break; 23 | case DateTimeConstants.Afternoon: 24 | result.Timex = DateTimeConstants.Afternoon; 25 | result.BeginHour = TasksModeConstants.AfternoonBeginHour; 26 | result.EndHour = TasksModeConstants.AfternoonEndHour; 27 | break; 28 | case DateTimeConstants.Evening: 29 | result.Timex = DateTimeConstants.Evening; 30 | result.BeginHour = TasksModeConstants.EveningBeginHour; 31 | result.EndHour = TasksModeConstants.EveningEndHour; 32 | break; 33 | case DateTimeConstants.Daytime: 34 | result.Timex = DateTimeConstants.Daytime; 35 | result.BeginHour = TasksModeConstants.DaytimeBeginHour; 36 | result.EndHour = TasksModeConstants.DaytimeEndHour; 37 | break; 38 | case DateTimeConstants.Nighttime: 39 | result.Timex = DateTimeConstants.Nighttime; 40 | result.BeginHour = TasksModeConstants.NighttimeBeginHour; 41 | result.EndHour = TasksModeConstants.NighttimeEndHour; 42 | break; 43 | case DateTimeConstants.BusinessHour: 44 | result.Timex = DateTimeConstants.BusinessHour; 45 | result.BeginHour = TasksModeConstants.BusinessBeginHour; 46 | result.EndHour = TasksModeConstants.BusinessEndHour; 47 | break; 48 | case DateTimeConstants.Night: 49 | result.Timex = DateTimeConstants.Night; 50 | result.BeginHour = TasksModeConstants.NightBeginHour; 51 | result.EndHour = TasksModeConstants.NightEndHour; 52 | result.EndMin = TasksModeConstants.NightEndMin; 53 | break; 54 | case DateTimeConstants.MealtimeBreakfast: 55 | result.Timex = DateTimeConstants.MealtimeBreakfast; 56 | result.BeginHour = TasksModeConstants.MealtimeBreakfastBeginHour; 57 | result.EndHour = TasksModeConstants.MealtimeBreakfastEndHour; 58 | break; 59 | case DateTimeConstants.MealtimeBrunch: 60 | result.Timex = DateTimeConstants.MealtimeBrunch; 61 | result.BeginHour = TasksModeConstants.MealtimeBrunchBeginHour; 62 | result.EndHour = TasksModeConstants.MealtimeBrunchEndHour; 63 | break; 64 | case DateTimeConstants.MealtimeLunch: 65 | result.Timex = DateTimeConstants.MealtimeLunch; 66 | result.BeginHour = TasksModeConstants.MealtimeLunchBeginHour; 67 | result.EndHour = TasksModeConstants.MealtimeLunchEndHour; 68 | break; 69 | case DateTimeConstants.MealtimeDinner: 70 | result.Timex = DateTimeConstants.MealtimeDinner; 71 | result.BeginHour = TasksModeConstants.MealtimeDinnerBeginHour; 72 | result.EndHour = TasksModeConstants.MealtimeDinnerEndHour; 73 | break; 74 | default: 75 | break; 76 | } 77 | 78 | return result; 79 | } 80 | 81 | /* 82 | Change beginHour and endHour for subjective time refereneces under TasksMode. 83 | morning get's mapped to 6:00 am 84 | */ 85 | static (int beginHour, int endHour, int endMin) GetMatchedTimeRangeForTasksMode(String text, String todSymbol) { 86 | var trimmedText = text.trim(); 87 | int beginHour = 0; 88 | int endHour = 0; 89 | int endMin = 0; 90 | if (todSymbol == DateTimeConstants.Morning) { 91 | beginHour = TasksModeConstants.MorningBeginHour; 92 | endHour = TasksModeConstants.EarlyMorningEndHour; 93 | } else if (todSymbol == DateTimeConstants.Afternoon) { 94 | beginHour = DateTimeConstants.AfternoonBeginHour; 95 | endHour = DateTimeConstants.AfternoonEndHour; 96 | } else if (todSymbol == DateTimeConstants.Evening) { 97 | beginHour = DateTimeConstants.EveningBeginHour; 98 | endHour = DateTimeConstants.EveningEndHour; 99 | } else if (todSymbol == DateTimeConstants.Night) { 100 | beginHour = TasksModeConstants.NightBeginHour; 101 | endHour = TasksModeConstants.NightEndHour; 102 | } else if (todSymbol == DateTimeConstants.MealtimeBreakfast) { 103 | beginHour = TasksModeConstants.MealtimeBreakfastBeginHour; 104 | endHour = TasksModeConstants.MealtimeBreakfastEndHour; 105 | } else if (todSymbol == DateTimeConstants.MealtimeBrunch) { 106 | beginHour = TasksModeConstants.MealtimeBrunchBeginHour; 107 | endHour = TasksModeConstants.MealtimeBrunchEndHour; 108 | } else if (todSymbol == DateTimeConstants.MealtimeDinner) { 109 | beginHour = TasksModeConstants.MealtimeDinnerBeginHour; 110 | endHour = TasksModeConstants.MealtimeDinnerEndHour; 111 | } else if (todSymbol == DateTimeConstants.MealtimeLunch) { 112 | beginHour = TasksModeConstants.MealtimeLunchBeginHour; 113 | endHour = TasksModeConstants.MealtimeLunchEndHour; 114 | } else { 115 | return (beginHour, endHour, endMin); 116 | } 117 | 118 | return (beginHour, endHour, endMin); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/date_time/base_datetime_options_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_date_extractor.dart'; 2 | import 'package:nlp/src/duration/duration.dart'; 3 | 4 | class BaseDateTimeOptionsConfiguration implements IDateTimeOptionsConfiguration { 5 | BaseDateTimeOptionsConfiguration({ 6 | required DateTimeOptions options, 7 | bool dmyDateFormat = false, 8 | }) : _options = options, 9 | _dmyDateFormat = dmyDateFormat; 10 | 11 | final bool _dmyDateFormat; 12 | @override 13 | bool get dmyDateFormat => _dmyDateFormat; 14 | 15 | final DateTimeOptions _options; 16 | @override 17 | DateTimeOptions get options => _options; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/date_time/base_datetime_utility_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/nlp.dart'; 2 | import 'package:nlp/src/date_time/datetime_utility_configuration.dart'; 3 | 4 | abstract class BaseDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { 5 | BaseDatetimeUtilityConfiguration( 6 | String agoRegex, 7 | String laterRegex, 8 | String inConnectorRegex, 9 | String sinceYearSuffixRegex, 10 | String withinNextPrefixRegex, 11 | String amDescRegex, 12 | String pmDescRegex, 13 | String amPmDescRegex, 14 | String rangeUnitRegex, 15 | String timeUnitRegex, 16 | String dateUnitRegex, 17 | String commonDatePrefixRegex, 18 | String rangePrefixRegex, 19 | //RegexOptions options, 20 | bool checkBothBeforeAfter) 21 | : _agoRegExp = RegExpComposer.sanitizeGroupsAndCompile(agoRegex), 22 | _laterRegExp = RegExpComposer.sanitizeGroupsAndCompile(laterRegex), 23 | _inConnectorRegExp = RegExpComposer.sanitizeGroupsAndCompile(inConnectorRegex), 24 | _sinceYearSuffixRegExp = RegExpComposer.sanitizeGroupsAndCompile(sinceYearSuffixRegex), 25 | _withinNextPrefixRegExp = RegExpComposer.sanitizeGroupsAndCompile(withinNextPrefixRegex), 26 | _amDescRegExp = RegExpComposer.sanitizeGroupsAndCompile(amDescRegex), 27 | _pmDescRegExp = RegExpComposer.sanitizeGroupsAndCompile(pmDescRegex), 28 | _amPmDescRegExp = RegExpComposer.sanitizeGroupsAndCompile(amPmDescRegex), 29 | _rangeUnitRegExp = RegExpComposer.sanitizeGroupsAndCompile(rangeUnitRegex), 30 | _timeUnitRegExp = RegExpComposer.sanitizeGroupsAndCompile(timeUnitRegex), 31 | _dateUnitRegExp = RegExpComposer.sanitizeGroupsAndCompile(dateUnitRegex), 32 | _commonDatePrefixRegExp = RegExpComposer.sanitizeGroupsAndCompile(commonDatePrefixRegex), 33 | _rangePrefixRegExp = RegExpComposer.sanitizeGroupsAndCompile(rangePrefixRegex), 34 | _checkBothBeforeAfter = checkBothBeforeAfter; 35 | 36 | // static TimeSpan RegexTimeOut => DateTimeRecognizer.GetTimeout(MethodBase.GetCurrentMethod().DeclaringType); 37 | 38 | final RegExp _agoRegExp; 39 | @override 40 | RegExp agoRegExp() => _agoRegExp; 41 | 42 | final RegExp _laterRegExp; 43 | @override 44 | RegExp laterRegExp() => _laterRegExp; 45 | 46 | final RegExp _inConnectorRegExp; 47 | @override 48 | RegExp inConnectorRegExp() => _inConnectorRegExp; 49 | 50 | final RegExp _sinceYearSuffixRegExp; 51 | @override 52 | RegExp sinceYearSuffixRegExp() => _sinceYearSuffixRegExp; 53 | 54 | final RegExp _withinNextPrefixRegExp; 55 | @override 56 | RegExp withinNextPrefixRegExp() => _withinNextPrefixRegExp; 57 | 58 | final RegExp _rangeUnitRegExp; 59 | @override 60 | RegExp rangeUnitRegExp() => _rangeUnitRegExp; 61 | 62 | final RegExp _timeUnitRegExp; 63 | @override 64 | RegExp timeUnitRegExp() => _timeUnitRegExp; 65 | 66 | final RegExp _dateUnitRegExp; 67 | @override 68 | RegExp dateUnitRegExp() => _dateUnitRegExp; 69 | 70 | final RegExp _amDescRegExp; 71 | @override 72 | RegExp amDescRegExp() => _amDescRegExp; 73 | 74 | final RegExp _pmDescRegExp; 75 | @override 76 | RegExp pmDescRegExp() => _pmDescRegExp; 77 | 78 | final RegExp _amPmDescRegExp; 79 | @override 80 | RegExp amPmDescRegExp() => _amPmDescRegExp; 81 | 82 | final RegExp _commonDatePrefixRegExp; 83 | @override 84 | RegExp commonDatePrefixRegExp() => _commonDatePrefixRegExp; 85 | 86 | final RegExp _rangePrefixRegExp; 87 | @override 88 | RegExp rangePrefixRegExp() => _rangePrefixRegExp; 89 | 90 | final bool _checkBothBeforeAfter; 91 | @override 92 | bool checkBothBeforeAfter() => _checkBothBeforeAfter; 93 | } 94 | -------------------------------------------------------------------------------- /lib/src/date_time/base_holiday_extractor.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/nlp.dart'; 2 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 3 | import 'package:nlp/src/date_time/holiday_extractor_configuration.dart'; 4 | 5 | class BaseHolidayExtractor implements IDateTimeExtractor { 6 | BaseHolidayExtractor({required this.config}); 7 | 8 | final IHolidayExtractorConfiguration config; 9 | 10 | final _rangeExtractorName = DateTimeConstants.SYS_DATETIME_DATEPERIOD; 11 | 12 | @override 13 | List extract(String input) { 14 | return extractDateTime(input, DateTime.now()); 15 | } 16 | 17 | @override 18 | List extractDateTime(String input, DateTime reference) { 19 | final tokens = []; 20 | tokens.addAll(_holidayMatch(input)); 21 | var ers = Token.mergeAllTokens(tokens, input, getExtractorName()); 22 | for (var er in ers) { 23 | // If this is a daterange that contains a holiday, we should change its 24 | // type to indicate that. 25 | 26 | if (er.metadata?.isHolidayRange ?? false) { 27 | er.type = _rangeExtractorName; 28 | } 29 | } 30 | 31 | return ers; 32 | } 33 | 34 | @override 35 | String getExtractorName() => DateTimeConstants.SYS_DATETIME_DATE; 36 | 37 | List _holidayMatch(String text) { 38 | var ret = []; 39 | for (var regex in config.holidayRegexes()) { 40 | var matches = RegExpComposer.getMatchesSimple(regex, text); 41 | 42 | for (final match in matches) { 43 | var metaData = Metadata(); 44 | 45 | // The objective here is to not lose the information of the holiday name 46 | // and year (if captured) when choosing. The data is extracted from the match 47 | // groups. 48 | final holidayWeekendGroup = match.getGroup(DateTimeConstants.HolidayWeekend); 49 | 50 | if (holidayWeekendGroup.value.isNotEmpty) { 51 | metaData.isHolidayRange = metaData.isHolidayWeekend = true; 52 | 53 | final holidayGroup = match.getGroup(DateTimeConstants.Holiday); 54 | metaData.holidayName = holidayGroup.value; 55 | 56 | final yearGroup = match.getGroup(DateTimeConstants.YearGroupName); 57 | if (yearGroup.value.isNotEmpty) { 58 | metaData.holidayName = "${metaData.holidayName} ${yearGroup.value}"; 59 | } 60 | } 61 | 62 | metaData.isHoliday = true; 63 | 64 | ret.add(Token(match.index, match.index + match.length, "", metaData)); 65 | } 66 | } 67 | 68 | return ret; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/date_time/base_time_extractor.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/date_time/base_date_extractor.dart'; 3 | import 'package:nlp/src/date_time/constants.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/date_time/extract_result_extension.dart'; 6 | import 'package:nlp/src/date_time/timezone_utility.dart'; 7 | import 'package:nlp/src/duration/duration.dart'; 8 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 9 | 10 | class BaseTimeExtractor implements IDateTimeExtractor { 11 | BaseTimeExtractor(this.config); 12 | 13 | final ITimeExtractorConfiguration config; 14 | 15 | @override 16 | String getExtractorName() { 17 | return DateTimeConstants.SYS_DATETIME_TIME; 18 | } 19 | 20 | @override 21 | List extract(String input) { 22 | return extractDateTime(input, DateTime.now()); 23 | } 24 | 25 | @override 26 | List extractDateTime(String input, DateTime reference) { 27 | final normalizedInput = input.toLowerCase(); 28 | 29 | var tokens = []; 30 | tokens.addAll(BasicRegexMatch(normalizedInput)); 31 | tokens.addAll(AtRegexMatch(normalizedInput)); 32 | tokens.addAll(BeforeAfterRegexMatch(normalizedInput)); 33 | tokens.addAll(SpecialCasesRegexMatch(normalizedInput, reference)); 34 | 35 | var timeErs = Token.mergeAllTokens(tokens, normalizedInput, getExtractorName()); 36 | 37 | if (config.options.match(DateTimeOptions.EnablePreview)) { 38 | timeErs = TimeZoneUtility.MergeTimeZones( 39 | timeErs, config.TimeZoneExtractor().extractDateTime(normalizedInput, reference), normalizedInput); 40 | } 41 | 42 | // Remove common ambiguous cases 43 | timeErs = ExtractResultExtension.FilterAmbiguity(timeErs, normalizedInput, config.AmbiguityFiltersDict()); 44 | 45 | return timeErs; 46 | } 47 | 48 | List BasicRegexMatch(String text) { 49 | var results = []; 50 | 51 | for (var regex in config.TimeRegexList()) { 52 | var matches = RegExpComposer.getMatchesSimple(regex, text); 53 | 54 | for (final match in matches) { 55 | // @TODO Workaround to avoid incorrect partial-only matches. Remove after time regex reviews across languages. 56 | var lth = match.getGroup("lth").value; 57 | 58 | if (lth.isEmpty || 59 | (lth.length != match.length && !(match.length == lth.length + 1 && match.value.endsWith(" ")))) { 60 | results.add(Token(match.index, match.index + match.length)); 61 | } 62 | } 63 | } 64 | 65 | return results; 66 | } 67 | 68 | List AtRegexMatch(String text) { 69 | var result = []; 70 | 71 | // handle "at 5", "at seven" 72 | if (config.AtRegex().hasMatch(text)) { 73 | var matches = config.AtRegex().allMatches(text); 74 | for (Match match in matches) { 75 | if (match.end - match.start + 1 < text.length && text[match.end - 1] == '%') { 76 | continue; 77 | } 78 | 79 | result.add(Token(match.start, match.end)); 80 | } 81 | } 82 | 83 | return result; 84 | } 85 | 86 | List BeforeAfterRegexMatch(String text) { 87 | var result = []; 88 | 89 | // only enabled in CalendarMode 90 | if (config.options.match(DateTimeOptions.CalendarMode)) { 91 | // handle "before 3", "after three" 92 | var beforeAfterRegex = config.TimeBeforeAfterRegex(); 93 | if (beforeAfterRegex.hasMatch(text)) { 94 | var matches = beforeAfterRegex.allMatches(text); 95 | for (Match match in matches) { 96 | result.add(Token(match.start, match.end)); 97 | } 98 | } 99 | } 100 | 101 | return result; 102 | } 103 | 104 | List SpecialCasesRegexMatch(String text, DateTime reference) { 105 | var result = []; 106 | 107 | // handle "ish" 108 | if (config.IshRegex().hasMatch(text)) { 109 | var matches = config.IshRegex().allMatches(text); 110 | 111 | for (Match match in matches) { 112 | result.add(Token(match.start, match.end)); 113 | } 114 | } 115 | 116 | return result; 117 | } 118 | 119 | // public static readonly Regex HourRegex = 120 | // new Regex(BaseDateTime.HourRegex, RegexOptions.Singleline | RegexOptions.Compiled, RegexTimeOut); 121 | 122 | // public static readonly Regex MinuteRegex = 123 | // new Regex(BaseDateTime.MinuteRegex, RegexOptions.Singleline | RegexOptions.Compiled, RegexTimeOut); 124 | 125 | // public static readonly Regex SecondRegex = 126 | // new Regex(BaseDateTime.SecondRegex, RegexOptions.Singleline | RegexOptions.Compiled, RegexTimeOut); 127 | } 128 | 129 | abstract interface class ITimeExtractorConfiguration extends IDateTimeOptionsConfiguration { 130 | IDateTimeExtractor TimeZoneExtractor(); 131 | 132 | List TimeRegexList(); 133 | 134 | RegExp AtRegex(); 135 | 136 | RegExp IshRegex(); 137 | 138 | RegExp TimeBeforeAfterRegex(); 139 | 140 | RegExp TimeTokenPrefix(); 141 | 142 | Map AmbiguityFiltersDict(); 143 | } 144 | -------------------------------------------------------------------------------- /lib/src/date_time/data_structure.dart: -------------------------------------------------------------------------------- 1 | enum DatePeriodTimexType { 2 | /// 3 | /// Represents a day Period 4 | /// 5 | ByDay, 6 | 7 | /// 8 | /// Represents a week Period 9 | /// 10 | ByWeek, 11 | 12 | /// 13 | /// Represents a fortnight Period 14 | /// 15 | ByFortnight, 16 | 17 | /// 18 | /// Represents a month Period 19 | /// 20 | ByMonth, 21 | 22 | /// 23 | /// Represents a year Period 24 | /// 25 | ByYear, 26 | } 27 | 28 | enum PeriodType { 29 | /// 30 | /// Represents a ShortTime. 31 | /// 32 | ShortTime, 33 | 34 | /// 35 | /// Represents a FullTime. 36 | /// 37 | FullTime, 38 | } 39 | 40 | enum TimeType { 41 | /// 42 | /// 十二点二十三分五十八秒,12点23分53秒 43 | /// 44 | CjkTime, 45 | 46 | /// 47 | /// 差五分十二点 48 | /// 49 | LessTime, 50 | 51 | /// 52 | /// 大约早上10:00 53 | /// 54 | DigitTime, 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/date_time/date_context.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/timex_utility.dart'; 2 | import 'package:nlp/src/date_time/base_date_parser.dart'; 3 | import 'package:nlp/src/date_time/constants.dart'; 4 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 5 | import 'package:nlp/src/date_time/date_time_recognizer.dart'; 6 | import 'package:nlp/src/date_time/date_util.dart'; 7 | 8 | class DateContext { 9 | DateContext({ 10 | this.year = DateTimeConstants.InvalidYear, 11 | }); 12 | 13 | final int year; 14 | 15 | static (DateTime future, DateTime past) GenerateDates( 16 | bool noYear, DateTime referenceDate, int year, int month, int day) { 17 | var futureDate = DateUtil.createSafeDate(year, month, day); 18 | var pastDate = DateUtil.createSafeDate(year, month, day); 19 | var futureYear = year; 20 | var pastYear = year; 21 | if (noYear) { 22 | if (IsFeb29th(year, month, day)) { 23 | if (isLeapYear(year)) { 24 | if (futureDate.isBefore(referenceDate)) { 25 | futureDate = DateTime(futureYear + 4, month, day); 26 | } else { 27 | pastDate = DateTime(pastYear - 4, month, day); 28 | } 29 | } else { 30 | pastYear = pastYear >> 2 << 2; 31 | if (!isLeapYear(pastYear)) { 32 | pastYear -= 4; 33 | } 34 | 35 | futureYear = pastYear + 4; 36 | if (!isLeapYear(futureYear)) { 37 | futureYear += 4; 38 | } 39 | 40 | futureDate = DateTime(futureYear, month, day); 41 | pastDate = DateTime(pastYear, month, day); 42 | } 43 | } else { 44 | if (futureDate.isBefore(referenceDate) && futureDate != DateUtil.minValue()) { 45 | futureDate = DateTime(year + 1, month, day); 46 | } 47 | 48 | if (!referenceDate.isAfter(pastDate) && pastDate != DateUtil.minValue()) { 49 | pastDate = DateTime(year - 1, month, day); 50 | } 51 | } 52 | } 53 | 54 | futureDate = adjustTimeZones(referenceDate, futureDate); 55 | pastDate = adjustTimeZones(referenceDate, pastDate); 56 | 57 | return (futureDate, pastDate); 58 | } 59 | 60 | static bool IsFeb29th(int year, int month, int day) { 61 | return month == 2 && day == 29; 62 | } 63 | 64 | static DateTime adjustTimeZones(DateTime original, DateTime dateTime) { 65 | final timeZoneOffset = original.timeZoneOffset; 66 | if (dateTime.timeZoneOffset != timeZoneOffset) { 67 | final newDate = dateTime.subtract(dateTime.timeZoneOffset).add(timeZoneOffset); 68 | } 69 | return dateTime; 70 | } 71 | 72 | static bool IsFeb29thDate(DateTime date) { 73 | return date.month == 2 && date.day == 29; 74 | } 75 | 76 | static bool isLeapYear(int year) { 77 | return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; 78 | } 79 | 80 | (DateTimeParseResult pr1, DateTimeParseResult pr2) SyncYear(DateTimeParseResult pr1, DateTimeParseResult pr2) { 81 | if (IsEmpty()) { 82 | int futureYear; 83 | int pastYear; 84 | if (IsFeb29thDate((pr1.value as DateTimeResolutionResult).futureValue as DateTime)) { 85 | futureYear = ((pr1.value as DateTimeResolutionResult).futureValue as DateTime).year; 86 | pastYear = ((pr1.value as DateTimeResolutionResult).pastValue as DateTime).year; 87 | pr2.value = SyncDateEntityResolutionInFeb29th(pr2.value as DateTimeResolutionResult, futureYear, pastYear); 88 | } else if (IsFeb29thDate((pr2.value as DateTimeResolutionResult).futureValue as DateTime)) { 89 | futureYear = ((pr2.value as DateTimeResolutionResult).futureValue as DateTime).year; 90 | pastYear = ((pr2.value as DateTimeResolutionResult).pastValue as DateTime).year; 91 | pr1.value = SyncDateEntityResolutionInFeb29th(pr1.value as DateTimeResolutionResult, futureYear, pastYear); 92 | } 93 | } 94 | 95 | return (pr1, pr2); 96 | } 97 | 98 | static DateTime SwiftDateObject(DateTime beginDate, DateTime endDate) { 99 | if (beginDate.isAfter(endDate)) { 100 | beginDate = beginDate.AddYears(-1); 101 | } 102 | 103 | return beginDate; 104 | } 105 | 106 | bool IsEmpty() { 107 | return year == DateTimeConstants.InvalidYear; 108 | } 109 | 110 | DateTimeResolutionResult ProcessDatePeriodEntityResolution(DateTimeResolutionResult resolutionResult) { 111 | if (!IsEmpty()) { 112 | resolutionResult.timex = TimexUtility.SetTimexWithContext(resolutionResult.timex ?? '', this); 113 | resolutionResult.futureValue = SetDateRangeWithContext(resolutionResult.futureValue as (DateTime, DateTime)); 114 | resolutionResult.pastValue = SetDateRangeWithContext(resolutionResult.pastValue as (DateTime, DateTime)); 115 | } 116 | 117 | return resolutionResult; 118 | } 119 | 120 | DateTimeResolutionResult ProcessDateEntityResolution(DateTimeResolutionResult resolutionResult) { 121 | if (!IsEmpty()) { 122 | resolutionResult.timex = TimexUtility.SetTimexWithContext(resolutionResult.timex ?? '', this); 123 | resolutionResult.futureValue = SetDateWithContext(resolutionResult.futureValue as DateTime); 124 | resolutionResult.pastValue = SetDateWithContext(resolutionResult.pastValue as DateTime); 125 | } 126 | 127 | return resolutionResult; 128 | } 129 | 130 | DateTimeParseResult ProcessDateEntityParsingResult(DateTimeParseResult originalResult) { 131 | if (!IsEmpty()) { 132 | originalResult.timexStr = TimexUtility.SetTimexWithContext(originalResult.timexStr ?? '', this); 133 | originalResult.value = ProcessDateEntityResolution(originalResult.value as DateTimeResolutionResult); 134 | } 135 | 136 | return originalResult; 137 | } 138 | 139 | DateTimeResolutionResult SyncDateEntityResolutionInFeb29th( 140 | DateTimeResolutionResult resolutionResult, int futureYear, int pastYear) { 141 | resolutionResult.futureValue = SetDateWithContext(resolutionResult.futureValue as DateTime, futureYear); 142 | resolutionResult.pastValue = SetDateWithContext(resolutionResult.pastValue as DateTime, pastYear); 143 | 144 | return resolutionResult; 145 | } 146 | 147 | (DateTime, DateTime) SetDateRangeWithContext((DateTime, DateTime) originalDateRange) { 148 | var startDate = SetDateWithContext(originalDateRange.$1); 149 | var endDate = SetDateWithContext(originalDateRange.$2); 150 | 151 | return (startDate, endDate); 152 | } 153 | 154 | DateTime SetDateWithContext(DateTime originalDate, [int year = -1]) { 155 | if (originalDate != DateUtil.minValue()) { 156 | return DateUtil.createSafeDate(year == -1 ? this.year : year, originalDate.month, originalDate.day); 157 | } 158 | 159 | return originalDate; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /lib/src/date_time/date_object_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_date_parser.dart'; 2 | import 'package:nlp/src/date_time/constants.dart'; 3 | import 'package:nlp/src/date_time/date_util.dart'; 4 | 5 | class DateObjectExtension { 6 | static const IndexOfLeapMonth = 1; 7 | static List MonthValidDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 8 | 9 | static DateTime GetFirstThursday(int year, [int month = DateTimeConstants.InvalidMonth]) { 10 | var targetMonth = month; 11 | 12 | if (month == DateTimeConstants.InvalidMonth) { 13 | targetMonth = 1; 14 | } 15 | 16 | var firstDay = DateUtil.createSafeDate(year, targetMonth, 1); 17 | DateTime firstThursday = firstDay.This(DateTime.thursday); 18 | 19 | // Thursday falls into previous year or previous month 20 | if (firstThursday.month != targetMonth) { 21 | firstThursday = firstDay.AddDays(DateTimeConstants.WeekDayCount); 22 | } 23 | 24 | return firstThursday; 25 | } 26 | 27 | static DateTime GetLastThursday(int year, [int month = DateTimeConstants.InvalidMonth]) { 28 | var targetMonth = month; 29 | 30 | if (month == DateTimeConstants.InvalidMonth) { 31 | targetMonth = 12; 32 | } 33 | 34 | var lastDay = GetLastDay(year, targetMonth); 35 | DateTime lastThursday = lastDay.This(DateTime.thursday); 36 | 37 | // Thursday falls into next year or next month 38 | if (lastThursday.month != targetMonth) { 39 | lastThursday = lastThursday.AddDays(-DateTimeConstants.WeekDayCount); 40 | } 41 | 42 | return lastThursday; 43 | } 44 | 45 | static DateTime GetLastDay(int year, int month) { 46 | month++; 47 | 48 | if (month == 13) { 49 | year++; 50 | month = 1; 51 | } 52 | 53 | var firstDayOfNextMonth = DateUtil.createSafeDate(year, month, 1); 54 | 55 | return firstDayOfNextMonth.AddDays(-1); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/date_time/date_parser_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/parser.dart'; 3 | import 'package:nlp/src/date_time/base_date_extractor.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 6 | import 'package:nlp/src/date_time/datetime_utility_configuration.dart'; 7 | 8 | abstract interface class IDateParserConfiguration extends IDateTimeOptionsConfiguration { 9 | String dateTokenPrefix(); 10 | 11 | IExtractor integerExtractor(); 12 | 13 | IExtractor ordinalExtractor(); 14 | 15 | IExtractor cardinalExtractor(); 16 | 17 | IParser numberParser(); 18 | 19 | IDateTimeExtractor durationExtractor(); 20 | 21 | IDateExtractor dateExtractor(); 22 | 23 | IDateTimeParser durationParser(); 24 | 25 | IDateTimeParser holidayParser(); 26 | 27 | List dateRegexes(); 28 | 29 | RegExp onRegExp(); 30 | 31 | RegExp specialDayRegExp(); 32 | 33 | RegExp specialDayWithNumRegExp(); 34 | 35 | RegExp nextRegExp(); 36 | 37 | RegExp thisRegExp(); 38 | 39 | RegExp lastRegExp(); 40 | 41 | RegExp unitRegExp(); 42 | 43 | RegExp upcomingPrefixRegExp(); 44 | 45 | RegExp pastPrefixRegExp(); 46 | 47 | RegExp weekDayRegExp(); 48 | 49 | RegExp monthRegExp(); 50 | 51 | RegExp weekDayOfMonthRegExp(); 52 | 53 | RegExp forTheRegExp(); 54 | 55 | RegExp weekDayAndDayOfMothRegExp(); 56 | 57 | RegExp weekDayAndDayRegExp(); 58 | 59 | RegExp relativeMonthRegExp(); 60 | 61 | RegExp strictRelativeRegExp(); 62 | 63 | RegExp yearSuffix(); 64 | 65 | RegExp relativeWeekDayRegExp(); 66 | 67 | RegExp relativeDayRegExp(); 68 | 69 | RegExp nextPrefixRegExp(); 70 | 71 | RegExp previousPrefixRegExp(); 72 | 73 | RegExp beforeAfterRegExp(); 74 | 75 | RegExp tasksModeDurationToDatePatterns(); 76 | 77 | Map unitMap(); 78 | 79 | Map dayOfMonth(); 80 | 81 | Map dayOfWeek(); 82 | 83 | Map monthOfYear(); 84 | 85 | Map cardinalMap(); 86 | 87 | List sameDayTerms(); 88 | 89 | List plusOneDayTerms(); 90 | 91 | List minusOneDayTerms(); 92 | 93 | List plusTwoDayTerms(); 94 | 95 | List minusTwoDayTerms(); 96 | 97 | bool checkBothBeforeAfter(); 98 | 99 | IDateTimeUtilityConfiguration utilityConfiguration(); 100 | 101 | int getSwiftMonthOrYear(String text); 102 | 103 | bool isCardinalLast(String text); 104 | 105 | String normalize(String text); 106 | } 107 | -------------------------------------------------------------------------------- /lib/src/date_time/date_time_extraction.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 3 | 4 | abstract interface class IDateExtractor extends IDateTimeExtractor { 5 | int getYearFromText(NlpMatch match); 6 | } 7 | 8 | abstract interface class IDateTimeExtractor implements IExtractor { 9 | String getExtractorName(); 10 | 11 | List extractDateTime(String input, DateTime reference); 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/date_time/date_time_parsing.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/parser.dart'; 3 | 4 | abstract interface class IDateTimeParser implements IParser { 5 | String getParserName(); 6 | 7 | DateTimeParseResult parseDateTime(ExtractResult extractResult, DateTime reference); 8 | 9 | List filterResults(String query, List candidateResults); 10 | } 11 | 12 | class DateTimeParseResult extends ParseResult { 13 | factory DateTimeParseResult.fromExtractResult(ExtractResult extractResult) { 14 | return DateTimeParseResult( 15 | start: extractResult.start, 16 | length: extractResult.length, 17 | text: extractResult.text, 18 | type: extractResult.type, 19 | data: extractResult.data, 20 | ); 21 | } 22 | 23 | factory DateTimeParseResult.fromParseResult(ParseResult parseResult) { 24 | return DateTimeParseResult( 25 | start: parseResult.start, 26 | length: parseResult.length, 27 | text: parseResult.text, 28 | type: parseResult.type, 29 | data: parseResult.data, 30 | value: parseResult.value, 31 | resolutionStr: parseResult.resolutionStr, 32 | ); 33 | } 34 | 35 | DateTimeParseResult({ 36 | required super.start, 37 | required super.length, 38 | required super.text, 39 | super.type, 40 | super.data, 41 | super.value, 42 | super.resolutionStr, 43 | this.timexStr, 44 | super.metadata, 45 | }); 46 | 47 | //TimexStr is only used in extractors related with date and time 48 | //It will output the TIMEX representation of a time string. 49 | String? timexStr; 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/date_time/date_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | import 'package:nlp/src/date_time/constants.dart'; 3 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 4 | 5 | class DateUtil { 6 | static final DateFormat DATE_TIME_FORMATTER = DateFormat("yyyy-MM-dd"); 7 | 8 | static final List _monthValidDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 9 | static const _leapMonth = 2; 10 | 11 | static DateTime minValue() { 12 | return DateTime(1, 1, 1, 0, 0, 0, 0); 13 | } 14 | 15 | static int daysInMonth(int year, int month) { 16 | final daysInMonth = month == _leapMonth // 17 | ? LeapMonthDays(year) 18 | : _monthValidDays[month - 1]; 19 | return daysInMonth; 20 | } 21 | 22 | static bool isValidDate(int year, int month, int day) { 23 | final yearStr = year.toString(); 24 | final monthStr = month.toString().padLeft(2, '0'); 25 | final dayStr = day.toString().padLeft(2, '0'); 26 | return DateTime.tryParse('$yearStr-$monthStr-$dayStr') != null; 27 | } 28 | 29 | static DateTime createSafeDate(int year, int month, int day, 30 | [int hour = 0, int minute = 0, int second = 0, int millisecond = 0]) { 31 | if (year < 1 || year > 9999) { 32 | return minValue(); 33 | } 34 | 35 | final daysInMonth = month == _leapMonth // 36 | ? LeapMonthDays(year) 37 | : _monthValidDays[month - 1]; 38 | 39 | if (month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth) { 40 | return DateTime(year, month, day, hour, minute, second, millisecond); 41 | } 42 | 43 | return minValue(); 44 | } 45 | 46 | static DateTime? tryParse(String? date) { 47 | if (date == null) { 48 | return null; 49 | } 50 | 51 | try { 52 | return DATE_TIME_FORMATTER.parse(date); 53 | } catch (exception) { 54 | return null; 55 | } 56 | } 57 | 58 | static bool validateMatch( 59 | NlpMatch match, String text, List dateRegExpList, RegExp rangeConnectorSymbolRegExp) { 60 | // If the match doesn't contains "year" part, it will not be ambiguous and it's a valid match 61 | final yearGroup = match.getGroup(DateTimeConstants.YearGroupName); 62 | var isValidMatch = yearGroup.value.isNotEmpty; 63 | 64 | if (!isValidMatch) { 65 | // If the "year" part is not at the end of the match, it's a valid match 66 | if (yearGroup.index + yearGroup.length != match.index + match.length) { 67 | isValidMatch = true; 68 | } else { 69 | var subText = text.substring(yearGroup.index); 70 | 71 | // If the following text (include the "year" part) doesn't start with a Date entity, it's a valid match 72 | if (!StartsWithBasicDate(subText, dateRegExpList)) { 73 | isValidMatch = true; 74 | } else { 75 | // 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 76 | // 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. 77 | subText = text.substring(yearGroup.index + yearGroup.length).trim(); 78 | subText = TrimStartRangeConnectorSymbols(subText, rangeConnectorSymbolRegExp); 79 | isValidMatch = StartsWithBasicDate(subText, dateRegExpList); 80 | } 81 | } 82 | 83 | // Expressions with mixed separators are not considered valid dates e.g. "30/4.85" (unless one is a comma "30/4, 2016") 84 | final dayGroup = match.getGroup(DateTimeConstants.DayGroupName); 85 | final monthGroup = match.getGroup(DateTimeConstants.MonthGroupName); 86 | 87 | if (dayGroup.value.isNotEmpty && monthGroup.value.isNotEmpty) { 88 | var noDateText = 89 | match.value.replaceAll(yearGroup.value, "").replaceAll(monthGroup.value, "").replaceAll(dayGroup.value, ""); 90 | 91 | final weekDayGroup = match.getGroup(DateTimeConstants.WeekdayGroupName); 92 | 93 | noDateText = weekDayGroup.value.isNotEmpty ? noDateText.replaceAll(weekDayGroup.value, "") : noDateText; 94 | final separators = ['/', '\\', '-', '.']; 95 | final separatorCount = separators.fold( 96 | 0, 97 | (count, separator) => count + (noDateText.contains(separator) ? 1 : 0), 98 | ); 99 | 100 | if (separatorCount > 1) { 101 | isValidMatch = false; 102 | } 103 | } 104 | } 105 | 106 | return isValidMatch; 107 | } 108 | 109 | static bool StartsWithBasicDate(String text, List dateRegexList) { 110 | for (final regex in dateRegexList) { 111 | var match = regex.matchBegin(text, true); 112 | 113 | if (match != null) { 114 | return true; 115 | } 116 | } 117 | 118 | return false; 119 | } 120 | 121 | static String TrimStartRangeConnectorSymbols(String text, RegExp rangeConnectorSymbolRegex) { 122 | final rangeConnectorSymbolMatches = RegExpComposer.getMatchesSimple(rangeConnectorSymbolRegex, text); 123 | 124 | for (final symbolMatch in rangeConnectorSymbolMatches) { 125 | var startSymbolLength = -1; 126 | 127 | if (symbolMatch.index == 0 && symbolMatch.length > startSymbolLength) { 128 | startSymbolLength = symbolMatch.length; 129 | } 130 | 131 | if (startSymbolLength > 0) { 132 | text = text.substring(startSymbolLength); 133 | } 134 | } 135 | 136 | return text.trim(); 137 | } 138 | 139 | static bool TrimStartRangeConnectorSymbolsStartsWithBasicDate(String text, List dateRegexList) { 140 | for (final regex in dateRegexList) { 141 | var match = regex.matchBegin(text, true); 142 | 143 | if (match != null) { 144 | return true; 145 | } 146 | } 147 | 148 | return false; 149 | } 150 | 151 | static int LeapMonthDays(int year) { 152 | return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? 29 : 28; 153 | } 154 | 155 | const DateUtil._(); 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/date_time/datetime_utility_configuration.dart: -------------------------------------------------------------------------------- 1 | abstract interface class IDateTimeUtilityConfiguration { 2 | RegExp agoRegExp(); 3 | 4 | RegExp laterRegExp(); 5 | 6 | RegExp inConnectorRegExp(); 7 | 8 | RegExp sinceYearSuffixRegExp(); 9 | 10 | RegExp withinNextPrefixRegExp(); 11 | 12 | RegExp rangeUnitRegExp(); 13 | 14 | RegExp timeUnitRegExp(); 15 | 16 | RegExp dateUnitRegExp(); 17 | 18 | RegExp amDescRegExp(); 19 | 20 | RegExp pmDescRegExp(); 21 | 22 | RegExp amPmDescRegExp(); 23 | 24 | RegExp commonDatePrefixRegExp(); 25 | 26 | RegExp rangePrefixRegExp(); 27 | 28 | bool checkBothBeforeAfter(); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_date_time_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/date_time/base_date_extractor.dart'; 3 | import 'package:nlp/src/date_time/base_datetime_extractor.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/date_time/datetime_utility_configuration.dart'; 6 | import 'package:nlp/src/date_time/english/english_date_time_utility_configuration.dart'; 7 | import 'package:nlp/src/date_time/english_date_extractor.dart'; 8 | import 'package:nlp/src/date_time/english_date_time.dart'; 9 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 10 | import 'package:nlp/src/duration/duration.dart'; 11 | import 'package:nlp/src/duration/duration_extractor.dart'; 12 | import 'package:nlp/src/numbers/numbers.dart'; 13 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 14 | 15 | class EnglishDateTimeExtractorConfiguration extends BaseOptionsConfiguration 16 | implements IDateTimeExtractorConfiguration { 17 | EnglishDateTimeExtractorConfiguration(this.config, [super.options = DateTimeOptions.None]) 18 | : _datePointExtractor = BaseDateExtractor(EnglishDateExtractorConfiguration(config)); 19 | 20 | final ICommonDateTimeParserConfiguration config; 21 | 22 | @override 23 | RegExp DateNumberConnectorRegExp() => 24 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.DateNumberConnectorRegex); 25 | 26 | final IDateExtractor _datePointExtractor; 27 | @override 28 | IDateExtractor DatePointExtractor() => _datePointExtractor; 29 | 30 | @override 31 | IDateTimeExtractor DurationExtractor() => config.durationExtractor; 32 | 33 | @override 34 | IDateTimeExtractor HolidayExtractor() => config.holidayExtractor; 35 | 36 | @override 37 | IExtractor integerExtractor() => IntegerExtractor.getInstance(); 38 | 39 | @override 40 | bool IsConnector(String text) { 41 | text = text.trim(); 42 | return text.isEmpty || 43 | RegExp(EnglishDateTime.PrepositionRegex).hasMatch(text) || 44 | RegExp(EnglishDateTime.ConnectorRegex).hasMatch(text); 45 | } 46 | 47 | @override 48 | RegExp NowRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NowRegex); 49 | 50 | @override 51 | RegExp NumberAsTimeRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NumberAsTimeRegex); 52 | 53 | @override 54 | RegExp SimpleTimeOfTodayAfterRegExp() => 55 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SimpleTimeOfTodayAfterRegex); 56 | 57 | @override 58 | RegExp SimpleTimeOfTodayBeforeRegExp() => 59 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SimpleTimeOfTodayBeforeRegex); 60 | 61 | @override 62 | RegExp SpecificEndOfRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificEndOfRegex); 63 | 64 | @override 65 | RegExp SuffixAfterRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SuffixAfterRegex); 66 | 67 | @override 68 | RegExp SuffixRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SuffixRegex); 69 | 70 | @override 71 | RegExp TimeOfDayRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeOfDayRegex); 72 | 73 | @override 74 | RegExp TimeOfTodayAfterRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeOfTodayAfterRegex); 75 | 76 | @override 77 | RegExp TimeOfTodayBeforeRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeOfTodayBeforeRegex); 78 | 79 | @override 80 | IDateTimeExtractor TimePointExtractor() => config.timeExtractor; 81 | 82 | @override 83 | RegExp UnitRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeUnitRegex); 84 | 85 | @override 86 | RegExp UnspecificEndOfRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.UnspecificEndOfRegex); 87 | 88 | @override 89 | IDateTimeUtilityConfiguration UtilityConfiguration() => EnglishDatetimeUtilityConfiguration(); 90 | 91 | @override 92 | RegExp YearRegExp() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.YearRegex); 93 | 94 | @override 95 | RegExp YearSuffix() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.YearSuffix); 96 | } 97 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_date_time_parser_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/parser.dart'; 3 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 4 | import 'package:nlp/src/date_time/base_datetime_parser.dart'; 5 | import 'package:nlp/src/date_time/constants.dart'; 6 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 7 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 8 | import 'package:nlp/src/date_time/datetime_utility_configuration.dart'; 9 | import 'package:nlp/src/date_time/english/english_date_time_utility_configuration.dart'; 10 | import 'package:nlp/src/date_time/english/english_holiday_extractor_configuration.dart'; 11 | import 'package:nlp/src/date_time/english_date_time.dart'; 12 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 13 | import 'package:nlp/src/duration/duration.dart'; 14 | import 'package:nlp/src/numbers/numbers.dart'; 15 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 16 | 17 | class EnglishDateTimeParserConfiguration extends BaseDateTimeOptionsConfiguration 18 | implements IDateTimeParserConfiguration { 19 | EnglishDateTimeParserConfiguration(this.config, {super.options = DateTimeOptions.None}); 20 | 21 | final ICommonDateTimeParserConfiguration config; 22 | 23 | @override 24 | RegExp AMTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.AMTimeRegex); 25 | 26 | @override 27 | IExtractor CardinalExtractor() => config.cardinalExtractor; 28 | 29 | @override 30 | bool ContainsAmbiguousToken(String text, String matchedText) => false; 31 | 32 | @override 33 | IDateExtractor DateExtractor() => config.dateExtractor; 34 | 35 | @override 36 | RegExp DateNumberConnectorRegex() => 37 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.DateNumberConnectorRegex); 38 | 39 | @override 40 | IDateTimeParser DateParser() => config.dateParser; 41 | 42 | @override 43 | IDateTimeExtractor DurationExtractor() => config.durationExtractor; 44 | 45 | @override 46 | IDateTimeParser DurationParser() => config.durationParser; 47 | 48 | @override 49 | int GetHour(String text, int hour) { 50 | int result = hour; 51 | 52 | var trimmedText = text.trim(); 53 | 54 | if (AMTimeRegex().matchEnd(trimmedText, true) != null && hour >= DateTimeConstants.HalfDayHourCount) { 55 | result -= DateTimeConstants.HalfDayHourCount; 56 | } else if (AMTimeRegex().matchEnd(trimmedText, true) == null && 57 | hour < DateTimeConstants.HalfDayHourCount && 58 | !(NightTimeRegex().matchEnd(trimmedText, true) != null && hour < DateTimeConstants.QuarterDayHourCount)) { 59 | result += DateTimeConstants.HalfDayHourCount; 60 | } 61 | 62 | return result; 63 | } 64 | 65 | @override 66 | String? GetMatchedNowTimex(String text) { 67 | var trimmedText = text.trim(); 68 | 69 | if (NowTimeRegex().matchEnd(trimmedText, true) != null) { 70 | return "PRESENT_REF"; 71 | } else if (RecentlyTimeRegex().matchExact(trimmedText, true) != null) { 72 | return "PAST_REF"; 73 | } else if (AsapTimeRegex().matchExact(trimmedText, true) != null) { 74 | return "FUTURE_REF"; 75 | } else { 76 | return null; 77 | } 78 | } 79 | 80 | @override 81 | int GetSwiftDay(String text) { 82 | var trimmedText = text.trim(); 83 | 84 | var swift = 0; 85 | if (NextPrefixRegex().matchBegin(trimmedText, true) != null) { 86 | swift = 1; 87 | } else if (PreviousPrefixRegex().matchBegin(trimmedText, true) != null) { 88 | swift = -1; 89 | } 90 | 91 | return swift; 92 | } 93 | 94 | @override 95 | IDateTimeExtractor HolidayExtractor() => config.holidayExtractor; 96 | 97 | @override 98 | IDateTimeParser HolidayTimeParser() { 99 | // TODO: implement HolidayTimeParser 100 | throw UnimplementedError(); 101 | } 102 | 103 | @override 104 | IExtractor integerExtractor() => IntegerExtractor.getInstance(); 105 | 106 | @override 107 | RegExp NowRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NowRegex); 108 | 109 | @override 110 | IParser NumberParser() => config.numberParser; 111 | 112 | @override 113 | Map Numbers() => config.numbers; 114 | 115 | @override 116 | RegExp PMTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PMTimeRegex); 117 | 118 | @override 119 | RegExp SimpleTimeOfTodayAfterRegex() => 120 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SimpleTimeOfTodayAfterRegex); 121 | 122 | @override 123 | RegExp SimpleTimeOfTodayBeforeRegex() => 124 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SimpleTimeOfTodayBeforeRegex); 125 | 126 | @override 127 | RegExp SpecificEndOfRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificEndOfRegex); 128 | 129 | @override 130 | RegExp SpecificTimeOfDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificTimeOfDayRegex); 131 | 132 | @override 133 | IDateTimeExtractor TimeExtractor() => config.timeExtractor; 134 | 135 | @override 136 | IDateTimeParser TimeParser() => config.timeParser; 137 | 138 | @override 139 | String TokenBeforeDate() => EnglishDateTime.TokenBeforeDate; 140 | 141 | @override 142 | String TokenBeforeTime() => EnglishDateTime.TokenBeforeTime; 143 | 144 | @override 145 | Map UnitMap() => config.unitMap; 146 | 147 | @override 148 | RegExp UnitRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeUnitRegex); 149 | 150 | @override 151 | RegExp UnspecificEndOfRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.UnspecificEndOfRegex); 152 | 153 | @override 154 | IDateTimeUtilityConfiguration UtilityConfiguration() => EnglishDatetimeUtilityConfiguration(); 155 | 156 | @override 157 | RegExp YearRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.YearRegex); 158 | 159 | RegExp NightTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NightTimeRegex); 160 | 161 | RegExp NowTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NowTimeRegex); 162 | 163 | RegExp RecentlyTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.RecentlyTimeRegex); 164 | 165 | RegExp AsapTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.AsapTimeRegex); 166 | 167 | RegExp NextPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NextPrefixRegex); 168 | 169 | RegExp PreviousPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PreviousPrefixRegex); 170 | } 171 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_date_time_period_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/date_time/base_date_extractor.dart'; 3 | import 'package:nlp/src/date_time/base_date_time_period_extractor.dart'; 4 | import 'package:nlp/src/date_time/base_datetime_extractor.dart'; 5 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 6 | import 'package:nlp/src/date_time/base_time_extractor.dart'; 7 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 8 | import 'package:nlp/src/date_time/english/english_date_time_extractor_configuration.dart'; 9 | import 'package:nlp/src/date_time/english/english_time_extractor_configuration.dart'; 10 | import 'package:nlp/src/date_time/english_date_extractor.dart'; 11 | import 'package:nlp/src/date_time/english_date_time.dart'; 12 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 13 | import 'package:nlp/src/duration/duration.dart'; 14 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 15 | 16 | class EnglishDateTimePeriodExtractorConfiguration extends BaseDateTimeOptionsConfiguration 17 | implements IDateTimePeriodExtractorConfiguration { 18 | EnglishDateTimePeriodExtractorConfiguration(this.config, {super.options = DateTimeOptions.None}); 19 | 20 | final ICommonDateTimeParserConfiguration config; 21 | 22 | @override 23 | RegExp AfterRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.AfterRegex); 24 | 25 | @override 26 | RegExp AmDescRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.AmDescRegex); 27 | 28 | @override 29 | RegExp BeforeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.BeforeRegex); 30 | 31 | @override 32 | IExtractor CardinalExtractor() => config.cardinalExtractor; 33 | 34 | @override 35 | bool CheckBothBeforeAfter() => EnglishDateTime.CheckBothBeforeAfter; 36 | 37 | @override 38 | RegExp DateUnitRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.DateUnitRegex); 39 | 40 | @override 41 | IDateTimeExtractor DurationExtractor() => config.durationExtractor; 42 | 43 | @override 44 | RegExp FollowedUnit() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.FollowedDateUnit); 45 | 46 | @override 47 | RegExp FutureSuffixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.FutureSuffixRegex); 48 | 49 | @override 50 | RegExp GeneralEndingRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.GeneralEndingRegex); 51 | 52 | @override 53 | int? GetBetweenTokenIndex(String text) { 54 | var betweenMatch = BetweenTokenRegex().firstMatch(text); 55 | if (betweenMatch != null) { 56 | return betweenMatch.start; 57 | } 58 | 59 | return null; 60 | } 61 | 62 | @override 63 | int? GetFromTokenIndex(String text) { 64 | var fromMatch = FromTokenRegex().firstMatch(text); 65 | if (fromMatch != null) { 66 | return fromMatch.start; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | @override 73 | bool HasConnectorToken(String text) { 74 | return RangeConnectorRegex().matchExact(text, true) != null; 75 | } 76 | 77 | @override 78 | IDateTimeExtractor HolidayExtractor() => config.holidayExtractor; 79 | 80 | @override 81 | RegExp MiddlePauseRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.MiddlePauseRegex); 82 | 83 | @override 84 | RegExp NextPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NextPrefixRegex); 85 | 86 | @override 87 | RegExp NumberCombinedWithUnit() => 88 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NumberCombinedWithDateUnit); 89 | 90 | @override 91 | RegExp PeriodTimeOfDayWithDateRegex() => 92 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PeriodTimeOfDayWithDateRegex); 93 | 94 | @override 95 | RegExp PmDescRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PmDescRegex); 96 | 97 | @override 98 | RegExp PrefixDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PrefixDayRegex); 99 | 100 | @override 101 | RegExp PrepositionRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PrepositionRegex); 102 | 103 | @override 104 | RegExp PreviousPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PreviousPrefixRegex); 105 | 106 | @override 107 | RegExp RelativeTimeUnitRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.RelativeTimeUnitRegex); 108 | 109 | @override 110 | RegExp RestOfDateTimeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.RestOfDateTimeRegex); 111 | 112 | @override 113 | List SimpleCasesRegex() => [ 114 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PureNumFromTo), 115 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PureNumBetweenAnd), 116 | ]; 117 | 118 | @override 119 | IDateTimeExtractor SingleDateExtractor() => BaseDateExtractor(EnglishDateExtractorConfiguration(config)); 120 | 121 | @override 122 | IDateTimeExtractor SingleDateTimeExtractor() => BaseDateTimeExtractor(EnglishDateTimeExtractorConfiguration(config)); 123 | 124 | @override 125 | IDateTimeExtractor SingleTimeExtractor() => BaseTimeExtractor(EnglishTimeExtractorConfiguration(config)); 126 | 127 | @override 128 | RegExp SpecificTimeOfDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificTimeOfDayRegex); 129 | 130 | @override 131 | RegExp SuffixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SuffixRegex); 132 | 133 | @override 134 | RegExp TasksmodeMealTimeofDayRegex() => 135 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TasksmodeMealTimeofDayRegex); 136 | 137 | @override 138 | RegExp TillRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TillRegex); 139 | 140 | @override 141 | RegExp TimeOfDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeOfDayRegex); 142 | 143 | @override 144 | IDateTimeExtractor TimePeriodExtractor() => config.timePeriodExtractor; 145 | 146 | @override 147 | RegExp TimeUnitRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeUnitRegex); 148 | 149 | @override 150 | IDateTimeExtractor TimeZoneExtractor() { 151 | // TODO: implement TimeZoneExtractor 152 | throw UnimplementedError(); 153 | } 154 | 155 | @override 156 | String TokenBeforeDate() => EnglishDateTime.TokenBeforeDate; 157 | 158 | @override 159 | RegExp WeekDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.WeekDayRegex); 160 | 161 | @override 162 | RegExp WithinNextPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.WithinNextPrefixRegex); 163 | 164 | RegExp BetweenTokenRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.BetweenTokenRegex); 165 | 166 | RegExp FromTokenRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.FromRegex); 167 | 168 | RegExp RangeConnectorRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.RangeConnectorRegex); 169 | } 170 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_date_time_utility_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_datetime_utility_configuration.dart'; 2 | import 'package:nlp/src/date_time/english_date_time.dart'; 3 | 4 | class EnglishDatetimeUtilityConfiguration extends BaseDatetimeUtilityConfiguration { 5 | EnglishDatetimeUtilityConfiguration() 6 | : super( 7 | EnglishDateTime.AgoRegex, 8 | EnglishDateTime.LaterRegex, 9 | EnglishDateTime.InConnectorRegex, 10 | EnglishDateTime.SinceYearSuffixRegex, 11 | EnglishDateTime.WithinNextPrefixRegex, 12 | EnglishDateTime.AmDescRegex, 13 | EnglishDateTime.PmDescRegex, 14 | EnglishDateTime.AmPmDescRegex, 15 | EnglishDateTime.RangeUnitRegex, 16 | EnglishDateTime.TimeUnitRegex, 17 | EnglishDateTime.DateUnitRegex, 18 | EnglishDateTime.CommonDatePrefixRegex, 19 | EnglishDateTime.RangePrefixRegex, 20 | EnglishDateTime.CheckBothBeforeAfter, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_holiday_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 2 | import 'package:nlp/src/date_time/english_date_time.dart'; 3 | import 'package:nlp/src/date_time/holiday_extractor_configuration.dart'; 4 | import 'package:nlp/src/duration/duration.dart'; 5 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 6 | 7 | class EnglishHolidayExtractorConfiguration extends BaseDateTimeOptionsConfiguration 8 | implements IHolidayExtractorConfiguration { 9 | static RegExp H = RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.HolidayRegex); 10 | 11 | EnglishHolidayExtractorConfiguration([DateTimeOptions options = DateTimeOptions.None]) 12 | : _holidayRegexes = [H], 13 | super(options: options); 14 | 15 | final List _holidayRegexes; 16 | @override 17 | List holidayRegexes() => _holidayRegexes; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_set_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 2 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 3 | import 'package:nlp/src/date_time/english_date_time.dart'; 4 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 5 | import 'package:nlp/src/duration/duration.dart'; 6 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 7 | import 'package:nlp/src/date_time/base_set_extractor.dart'; 8 | import 'package:nlp/src/date_time/set_handler.dart'; 9 | 10 | class EnglishSetExtractorConfiguration extends BaseDateTimeOptionsConfiguration implements ISetExtractorConfiguration { 11 | EnglishSetExtractorConfiguration(this.config, {super.options = DateTimeOptions.None}); 12 | 13 | final ICommonDateTimeParserConfiguration config; 14 | 15 | @override 16 | RegExp BeforeEachDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.DayRegex); 17 | 18 | @override 19 | bool CheckBothBeforeAfter() => EnglishDateTime.CheckBothBeforeAfter; 20 | 21 | @override 22 | IDateExtractor DateExtractor() => config.dateExtractor; 23 | 24 | @override 25 | IDateTimeExtractor DatePeriodExtractor() => config.datePeriodExtractor; 26 | 27 | @override 28 | IDateTimeExtractor DateTimeExtractor() => config.dateTimeExtractor; 29 | 30 | @override 31 | IDateTimeExtractor DateTimePeriodExtractor() => config.dateTimePeriodExtractor; 32 | 33 | @override 34 | IDateTimeExtractor DurationExtractor() => config.durationExtractor; 35 | 36 | @override 37 | RegExp EachDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.EachDayRegex); 38 | 39 | @override 40 | RegExp EachPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.EachPrefixRegex); 41 | 42 | @override 43 | RegExp EachUnitRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.EachUnitRegex); 44 | 45 | @override 46 | RegExp LastRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.FirstLastRegex); 47 | 48 | @override 49 | RegExp PeriodicRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PeriodicRegex); 50 | 51 | @override 52 | RegExp SetEachRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SetEachRegex); 53 | 54 | @override 55 | RegExp SetWeekDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SetWeekDayRegex); 56 | 57 | @override 58 | IDateTimeExtractor TimeExtractor() => config.timeExtractor; 59 | 60 | @override 61 | IDateTimeExtractor TimePeriodExtractor() => config.timePeriodExtractor; 62 | 63 | @override 64 | (String, int) WeekDayGroupMatchTuple(NlpMatch match) => SetHandler.WeekDayGroupMatchTuple(match); 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_set_parser_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/timex_utility.dart'; 2 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 3 | import 'package:nlp/src/date_time/base_set_parser.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 6 | import 'package:nlp/src/date_time/english_date_time.dart'; 7 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 8 | import 'package:nlp/src/date_time/set_handler.dart'; 9 | import 'package:nlp/src/date_time/task_mode_set_handler.dart'; 10 | import 'package:nlp/src/duration/duration.dart'; 11 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 12 | 13 | class EnglishSetParserConfiguration extends BaseDateTimeOptionsConfiguration implements ISetParserConfiguration { 14 | EnglishSetParserConfiguration(this.config, {super.options = DateTimeOptions.None}); 15 | 16 | final ICommonDateTimeParserConfiguration config; 17 | 18 | @override 19 | IDateExtractor DateExtractor() => config.dateExtractor; 20 | 21 | @override 22 | IDateTimeParser DateParser() => config.dateParser; 23 | 24 | @override 25 | IDateTimeExtractor DatePeriodExtractor() => config.datePeriodExtractor; 26 | 27 | @override 28 | IDateTimeParser DatePeriodParser() => config.datePeriodParser; 29 | 30 | @override 31 | IDateTimeExtractor DateTimeExtractor() => config.dateTimeExtractor; 32 | 33 | @override 34 | IDateTimeParser DateTimeParser() => config.dateTimeParser; 35 | 36 | @override 37 | IDateTimeExtractor DateTimePeriodExtractor() => config.dateTimePeriodExtractor; 38 | 39 | @override 40 | IDateTimeParser DateTimePeriodParser() => config.dateTimePeriodParser; 41 | 42 | @override 43 | IDateTimeExtractor DurationExtractor() => config.durationExtractor; 44 | 45 | @override 46 | IDateTimeParser DurationParser() => config.durationParser; 47 | 48 | @override 49 | RegExp EachDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.EachDayRegex); 50 | 51 | @override 52 | RegExp EachPrefixRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.EachPrefixRegex); 53 | 54 | @override 55 | RegExp EachUnitRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.EachUnitRegex); 56 | 57 | @override 58 | String? GetMatchedDailyTimex(String text) { 59 | var trimmedText = text.trim(); 60 | String? timex; 61 | 62 | double durationLength = 1; // Default value 63 | double multiplier = 1; 64 | String durationType; 65 | 66 | if (DoubleMultiplierRegex().hasMatch(trimmedText)) { 67 | multiplier = 2; 68 | } else if (HalfMultiplierRegex().hasMatch(trimmedText)) { 69 | multiplier = 0.5; 70 | } 71 | 72 | if (WeekDayTypeRegex().hasMatch(trimmedText)) { 73 | durationType = EnglishDateTime.UnitMap["weekday"]!; 74 | } else if (DayTypeRegex().hasMatch(trimmedText)) { 75 | durationType = EnglishDateTime.UnitMap["day"]!; 76 | } else if (WeekTypeRegex().hasMatch(trimmedText)) { 77 | durationType = EnglishDateTime.UnitMap["week"]!; 78 | } else if (WeekendTypeRegex().hasMatch(trimmedText)) { 79 | durationType = EnglishDateTime.UnitMap["weekend"]!; 80 | } else if (FortNightRegex().hasMatch(trimmedText)) { 81 | durationLength = 2; 82 | durationType = EnglishDateTime.UnitMap["week"]!; 83 | } else if (MonthTypeRegex().hasMatch(trimmedText)) { 84 | durationType = EnglishDateTime.UnitMap["m"]!; 85 | } else if (QuarterTypeRegex().hasMatch(trimmedText)) { 86 | durationLength = 3; 87 | durationType = EnglishDateTime.UnitMap["m"]!; 88 | } else if (YearTypeRegex().hasMatch(trimmedText)) { 89 | durationType = EnglishDateTime.UnitMap["y"]!; 90 | } else { 91 | return null; 92 | } 93 | 94 | timex = TimexUtility.GenerateSetTimex(durationType, durationLength, multiplier); 95 | 96 | return timex; 97 | } 98 | 99 | @override 100 | String? GetMatchedUnitTimex(String text) { 101 | return GetMatchedDailyTimex(text); 102 | } 103 | 104 | @override 105 | RegExp PeriodicRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PeriodicRegex); 106 | 107 | @override 108 | String ReplaceValueInTextWithFutTerm(String text, String value) { 109 | return TasksModeSetHandler.ReplaceValueInTextWithFutTerm(text, value, EnglishDateTime.FutureTerms); 110 | } 111 | 112 | @override 113 | RegExp SetEachRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SetEachRegex); 114 | 115 | @override 116 | RegExp SetWeekDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SetWeekDayRegex); 117 | 118 | @override 119 | IDateTimeExtractor TimeExtractor() => config.timeExtractor; 120 | 121 | @override 122 | IDateTimeParser TimeParser() => config.timeParser; 123 | 124 | @override 125 | IDateTimeExtractor TimePeriodExtractor() => config.timePeriodExtractor; 126 | 127 | @override 128 | IDateTimeParser TimePeriodParser() => config.timePeriodParser; 129 | 130 | @override 131 | Map UnitMap() => config.unitMap; 132 | 133 | @override 134 | String WeekDayGroupMatchString(NlpMatch match) { 135 | return SetHandler.WeekDayGroupMatchString(match); 136 | } 137 | 138 | RegExp DoubleMultiplierRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.DoubleMultiplierRegex); 139 | 140 | RegExp HalfMultiplierRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.HalfMultiplierRegex); 141 | 142 | RegExp YearTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.YearTypeRegex); 143 | 144 | RegExp MonthTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.MonthTypeRegex); 145 | 146 | RegExp QuarterTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.QuarterTypeRegex); 147 | 148 | RegExp WeekTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.WeekTypeRegex); 149 | 150 | RegExp WeekendTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.WeekendTypeRegex); 151 | 152 | RegExp WeekDayTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.WeekDayTypeRegex); 153 | 154 | RegExp DayTypeRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.DayTypeRegex); 155 | 156 | RegExp FortNightRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.FortNightRegex); 157 | } 158 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_time_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/definition_loader.dart'; 2 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 3 | import 'package:nlp/src/date_time/base_time_extractor.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/date_time/english_date_time.dart'; 6 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 7 | import 'package:nlp/src/duration/duration.dart'; 8 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 9 | 10 | class EnglishTimeExtractorConfiguration extends BaseDateTimeOptionsConfiguration 11 | implements ITimeExtractorConfiguration { 12 | EnglishTimeExtractorConfiguration(this.config, {super.options = DateTimeOptions.None}); 13 | 14 | final ICommonDateTimeParserConfiguration config; 15 | 16 | @override 17 | Map AmbiguityFiltersDict() => 18 | DefinitionLoader.LoadAmbiguityFilters(EnglishDateTime.AmbiguityDurationFiltersDict); 19 | 20 | @override 21 | RegExp AtRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.AtRegex); 22 | 23 | @override 24 | RegExp IshRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.IshRegex); 25 | 26 | @override 27 | RegExp TimeBeforeAfterRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeBeforeAfterRegex); 28 | 29 | @override 30 | List TimeRegexList() => [ 31 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex1), 32 | 33 | // (three min past)? 3:00(:00)? (pm)? 34 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex2), 35 | 36 | // (three min past)? 3.00 (pm) 37 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex3), 38 | 39 | // (three min past) (five thirty|seven|7|7:00(:00)?) (pm)? (in the night) 40 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex4), 41 | 42 | // (three min past) (five thirty|seven|7|7:00(:00)?) (pm)? 43 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex5), 44 | 45 | // (five thirty|seven|7|7:00(:00)?) (pm)? (in the night) 46 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex6), 47 | 48 | // (in the night) at? (five thirty|seven|7|7:00(:00)?) (pm)? 49 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex7), 50 | 51 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex9), 52 | 53 | // (three min past)? 3h00 (pm)? 54 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex10), 55 | 56 | // at 2.30, "at" prefix is required here 57 | // 3.30pm, "am/pm" suffix is required here 58 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeRegex11), 59 | 60 | // 340pm 61 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.ConnectNumRegex), 62 | ]; 63 | 64 | @override 65 | RegExp TimeTokenPrefix() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeTokenPrefix); 66 | 67 | @override 68 | IDateTimeExtractor TimeZoneExtractor() { 69 | // TODO: implement TimeZoneExtractor 70 | throw UnimplementedError(); 71 | } 72 | 73 | static RegExp LessThanOneHour() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.LessThanOneHour); 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_time_parser_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 2 | import 'package:nlp/src/date_time/base_time_parser.dart'; 3 | import 'package:nlp/src/date_time/constants.dart'; 4 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 5 | import 'package:nlp/src/date_time/datetime_utility_configuration.dart'; 6 | import 'package:nlp/src/date_time/english/english_date_time_utility_configuration.dart'; 7 | import 'package:nlp/src/date_time/english/english_time_extractor_configuration.dart'; 8 | import 'package:nlp/src/date_time/english_date_time.dart'; 9 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 10 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 11 | 12 | class EnglishTimeParserConfiguration extends BaseDateTimeOptionsConfiguration implements ITimeParserConfiguration { 13 | EnglishTimeParserConfiguration(this.config, {required super.options}); 14 | 15 | final ICommonDateTimeParserConfiguration config; 16 | 17 | @override 18 | (int hour, int min, bool hasMin) AdjustByPrefix(String prefix, int hour, int min, bool hasMin) { 19 | int deltaMin; 20 | 21 | var trimedPrefix = prefix.trim(); 22 | 23 | if (HalfTokenRegex().hasMatch(trimedPrefix)) { 24 | deltaMin = 30; 25 | } else if (QuarterTokenRegex().hasMatch(trimedPrefix)) { 26 | deltaMin = 15; 27 | } else if (ThreeQuarterTokenRegex().hasMatch(trimedPrefix)) { 28 | deltaMin = 45; 29 | } else { 30 | var match = 31 | RegExpComposer.getMatchesSimple(EnglishTimeExtractorConfiguration.LessThanOneHour(), trimedPrefix).first; 32 | var minStr = match.getGroup("deltamin").value; 33 | if (minStr.isNotEmpty) { 34 | deltaMin = int.parse(minStr); 35 | } else { 36 | minStr = match.getGroup("deltaminnum").value; 37 | deltaMin = Numbers()[minStr]!; 38 | } 39 | } 40 | 41 | if (ToTokenRegex().hasMatch(trimedPrefix)) { 42 | deltaMin = -deltaMin; 43 | } 44 | 45 | int adjustedMin = min; 46 | int adjustedHour = hour; 47 | 48 | adjustedMin += deltaMin; 49 | if (adjustedMin < 0) { 50 | adjustedMin += 60; 51 | adjustedHour -= 1; 52 | } 53 | 54 | return (adjustedHour, adjustedMin, true); 55 | } 56 | 57 | @override 58 | (int hour, int min, bool hasMin, bool hasAm, bool hasPm) AdjustBySuffix( 59 | String suffix, int hour, int min, bool hasMin, bool hasAm, bool hasPm) { 60 | var deltaHour = 0; 61 | var match = RegExpComposer.matchExact(TimeSuffixFull(), suffix, true); 62 | 63 | bool adjustedHasAm = hasAm; 64 | bool adjustedHasPm = hasPm; 65 | bool adjustedHasMin = hasMin; 66 | 67 | int adjustedHour = hour; 68 | int adjustedMin = min; 69 | 70 | if (match != null) { 71 | var oclockStr = match.getGroup("oclock").value; 72 | if (oclockStr.isEmpty) { 73 | var matchAmStr = match.getGroup(DateTimeConstants.AmGroupName).value; 74 | if (matchAmStr.isNotEmpty) { 75 | if (hour >= DateTimeConstants.HalfDayHourCount) { 76 | deltaHour = -DateTimeConstants.HalfDayHourCount; 77 | } else { 78 | adjustedHasAm = true; 79 | } 80 | } 81 | 82 | var matchPmStr = match.getGroup(DateTimeConstants.PmGroupName).value; 83 | if (matchPmStr.isNotEmpty) { 84 | if (hour < DateTimeConstants.HalfDayHourCount) { 85 | deltaHour = DateTimeConstants.HalfDayHourCount; 86 | } 87 | 88 | if (LunchRegex().hasMatch(matchPmStr)) { 89 | if (hour >= 10 && hour <= DateTimeConstants.HalfDayHourCount) { 90 | deltaHour = 0; 91 | if (hour == DateTimeConstants.HalfDayHourCount) { 92 | adjustedHasPm = true; 93 | } else { 94 | adjustedHasAm = true; 95 | } 96 | } else { 97 | adjustedHasPm = true; 98 | } 99 | } else if (NightRegex().hasMatch(matchPmStr)) { 100 | if (hour <= 3 || hour == DateTimeConstants.HalfDayHourCount) { 101 | if (hour == DateTimeConstants.HalfDayHourCount) { 102 | adjustedHour = 0; 103 | } 104 | 105 | deltaHour = 0; 106 | adjustedHasAm = true; 107 | } else { 108 | adjustedHasPm = true; 109 | } 110 | } else { 111 | adjustedHasPm = true; 112 | } 113 | } 114 | } 115 | } 116 | 117 | adjustedHour = (adjustedHour + deltaHour) % 24; 118 | 119 | return (adjustedHour, adjustedMin, adjustedHasMin, adjustedHasAm, adjustedHasPm); 120 | } 121 | 122 | @override 123 | RegExp AtRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.AtRegex); 124 | 125 | @override 126 | Map Numbers() => config.numbers; 127 | 128 | @override 129 | List TimeRegexes() => EnglishTimeExtractorConfiguration(config).TimeRegexList(); 130 | 131 | @override 132 | String TimeTokenPrefix() => EnglishDateTime.TimeTokenPrefix; 133 | 134 | @override 135 | IDateTimeParser TimeZoneParser() { 136 | // TODO: implement TimeZoneParser 137 | throw UnimplementedError(); 138 | } 139 | 140 | @override 141 | IDateTimeUtilityConfiguration UtilityConfiguration() => EnglishDatetimeUtilityConfiguration(); 142 | 143 | RegExp HalfTokenRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.HalfTokenRegex); 144 | 145 | RegExp QuarterTokenRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.QuarterTokenRegex); 146 | 147 | RegExp ThreeQuarterTokenRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.ThreeQuarterTokenRegex); 148 | 149 | RegExp ToTokenRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.ToTokenRegex); 150 | 151 | RegExp TimeSuffixFull() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeSuffixFull); 152 | 153 | RegExp LunchRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.LunchRegex); 154 | 155 | RegExp NightRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.NightRegex); 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_time_period_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/nlp.dart'; 2 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 3 | import 'package:nlp/src/date_time/base_time_extractor.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/date_time/english/english_time_extractor_configuration.dart'; 6 | import 'package:nlp/src/date_time/time_period_functions.dart'; 7 | 8 | class EnglishTimePeriodExtractorConfiguration extends BaseDateTimeOptionsConfiguration 9 | implements ITimePeriodExtractorConfiguration { 10 | EnglishTimePeriodExtractorConfiguration(this.config, {super.options = DateTimeOptions.None}); 11 | 12 | final ICommonDateTimeParserConfiguration config; 13 | 14 | @override 15 | List ApplyPotentialPeriodAmbiguityHotfix(String text, List timePeriodErs) => 16 | TimePeriodFunctions.ApplyPotentialPeriodAmbiguityHotfix(text, timePeriodErs); 17 | 18 | @override 19 | bool CheckBothBeforeAfter() => EnglishDateTime.CheckBothBeforeAfter; 20 | 21 | @override 22 | RegExp GeneralEndingRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.GeneralEndingRegex); 23 | 24 | @override 25 | int? GetBetweenTokenIndex(String text) { 26 | if (text.endsWith("between")) { 27 | return text.lastIndexOf("between"); 28 | } 29 | 30 | return null; 31 | } 32 | 33 | @override 34 | int? GetFromTokenIndex(String text) { 35 | if (text.endsWith("from")) { 36 | return text.lastIndexOf("from"); 37 | } 38 | 39 | return null; 40 | } 41 | 42 | @override 43 | IExtractor integerExtractor() => IntegerExtractor.getInstance(); 44 | 45 | @override 46 | bool IsConnectorToken(String text) { 47 | return text == "and"; 48 | } 49 | 50 | @override 51 | List PureNumberRegex() => [PureNumFromTo(), PureNumBetweenAnd()]; 52 | 53 | @override 54 | List SimpleCasesRegex() => 55 | [PureNumFromTo(), PureNumBetweenAnd(), SpecificTimeFromTo(), SpecificTimeBetweenAnd()]; 56 | 57 | @override 58 | IDateTimeExtractor SingleTimeExtractor() => BaseTimeExtractor(EnglishTimeExtractorConfiguration(config)); 59 | 60 | @override 61 | RegExp TillRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TillRegex); 62 | 63 | @override 64 | RegExp TimeOfDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeOfDayRegex); 65 | 66 | @override 67 | IDateTimeExtractor TimeZoneExtractor() => BaseTimeZoneExtractor(EnglishTimeZoneExtractorConfiguration()); 68 | 69 | @override 70 | String TokenBeforeDate() => EnglishDateTime.TokenBeforeDate; 71 | 72 | RegExp PureNumFromTo() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PureNumFromTo); 73 | 74 | RegExp PureNumBetweenAnd() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PureNumBetweenAnd); 75 | 76 | RegExp SpecificTimeFromTo() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificTimeFromTo); 77 | 78 | RegExp SpecificTimeBetweenAnd() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificTimeBetweenAnd); 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/date_time/english/english_time_period_parser_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/task_mode_processing.dart'; 3 | import 'package:nlp/src/core/timex_utility.dart'; 4 | import 'package:nlp/src/date_time/base_datetime_options_configuration.dart'; 5 | import 'package:nlp/src/date_time/base_time_period_parser.dart'; 6 | import 'package:nlp/src/date_time/constants.dart'; 7 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 8 | import 'package:nlp/src/date_time/date_time_parsing.dart'; 9 | import 'package:nlp/src/date_time/datetime_utility_configuration.dart'; 10 | import 'package:nlp/src/date_time/english/english_date_time_utility_configuration.dart'; 11 | import 'package:nlp/src/date_time/english_date_time.dart'; 12 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 13 | import 'package:nlp/src/duration/duration.dart'; 14 | import 'package:nlp/src/numbers/numbers.dart'; 15 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 16 | 17 | class EnglishTimePeriodParserConfiguration extends BaseDateTimeOptionsConfiguration 18 | implements ITimePeriodParserConfiguration { 19 | EnglishTimePeriodParserConfiguration(this.config, {super.options = DateTimeOptions.None}); 20 | 21 | final ICommonDateTimeParserConfiguration config; 22 | 23 | @override 24 | RegExp GeneralEndingRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.GeneralEndingRegex); 25 | 26 | @override 27 | (String, int, int, int) GetMatchedTimeRange(String text) { 28 | var trimmedText = text.trim(); 29 | if (trimmedText.endsWith("s")) { 30 | trimmedText = trimmedText.substring(0, trimmedText.length - 1); 31 | } 32 | 33 | int beginHour = 0; 34 | int endHour = 0; 35 | int endMin = 0; 36 | String timex = ''; 37 | 38 | var timeOfDay = ''; 39 | if (EnglishDateTime.MorningTermList.where((o) => trimmedText.endsWith(o)).isNotEmpty) { 40 | timeOfDay = DateTimeConstants.Morning; 41 | } else if (EnglishDateTime.AfternoonTermList.where((o) => trimmedText.endsWith(o)).isNotEmpty) { 42 | timeOfDay = DateTimeConstants.Afternoon; 43 | } else if (EnglishDateTime.EveningTermList.where((o) => trimmedText.endsWith(o)).isNotEmpty) { 44 | timeOfDay = DateTimeConstants.Evening; 45 | } else if (EnglishDateTime.DaytimeTermList.where((o) => trimmedText.endsWith(o)).isNotEmpty) { 46 | timeOfDay = DateTimeConstants.Daytime; 47 | } else if (EnglishDateTime.NighttimeTermList.where((o) => trimmedText.endsWith(o)).isNotEmpty) { 48 | timeOfDay = DateTimeConstants.Nighttime; 49 | } else if (EnglishDateTime.NightTermList.where((o) => trimmedText.endsWith(o)).isNotEmpty) { 50 | timeOfDay = DateTimeConstants.Night; 51 | } else if (EnglishDateTime.BusinessHourSplitStrings.where((o) => trimmedText.contains(o)).length == 52 | EnglishDateTime.BusinessHourSplitStrings.length) { 53 | timeOfDay = DateTimeConstants.BusinessHour; 54 | } else if (EnglishDateTime.MealtimeBreakfastTermList.where((o) => trimmedText.contains(o)).isNotEmpty) { 55 | timeOfDay = DateTimeConstants.MealtimeBreakfast; 56 | } else if (EnglishDateTime.MealtimeBrunchTermList.where((o) => trimmedText.contains(o)).isNotEmpty) { 57 | timeOfDay = DateTimeConstants.MealtimeBrunch; 58 | } else if (EnglishDateTime.MealtimeLunchTermList.where((o) => trimmedText.contains(o)).isNotEmpty) { 59 | timeOfDay = DateTimeConstants.MealtimeLunch; 60 | } else if (EnglishDateTime.MealtimeDinnerTermList.where((o) => trimmedText.contains(o)).isNotEmpty) { 61 | timeOfDay = DateTimeConstants.MealtimeDinner; 62 | } else { 63 | return ('', 0, 0, 0); 64 | } 65 | 66 | var parseResult = TimexUtility.ResolveTimeOfDay(timeOfDay); 67 | timex = parseResult.Timex; 68 | beginHour = parseResult.BeginHour; 69 | endHour = parseResult.EndHour; 70 | endMin = parseResult.EndMin; 71 | 72 | if (options.match(DateTimeOptions.TasksMode)) { 73 | beginHour = 0; 74 | endHour = 0; 75 | endMin = 0; 76 | parseResult = TasksModeProcessing.TasksModeResolveTimeOfDay(timeOfDay); 77 | timex = parseResult.Timex; 78 | beginHour = parseResult.BeginHour; 79 | endHour = parseResult.EndHour; 80 | endMin = parseResult.EndMin; 81 | } 82 | 83 | return (timex, beginHour, endHour, endMin); 84 | } 85 | 86 | @override 87 | IExtractor integerExtractor() => IntegerExtractor.getInstance(); 88 | 89 | @override 90 | Map Numbers() => config.numbers; 91 | 92 | @override 93 | RegExp PureNumberBetweenAndRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PureNumBetweenAnd); 94 | 95 | @override 96 | RegExp PureNumberFromToRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.PureNumFromTo); 97 | 98 | @override 99 | RegExp SpecificTimeBetweenAndRegex() => 100 | RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificTimeBetweenAnd); 101 | 102 | @override 103 | RegExp SpecificTimeFromToRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.SpecificTimeFromTo); 104 | 105 | @override 106 | RegExp TillRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TillRegex); 107 | 108 | @override 109 | IDateTimeExtractor TimeExtractor() => config.timeExtractor; 110 | 111 | @override 112 | RegExp TimeOfDayRegex() => RegExpComposer.sanitizeGroupsAndCompile(EnglishDateTime.TimeOfDayRegex); 113 | 114 | @override 115 | IDateTimeParser TimeParser() => config.timeParser; 116 | 117 | @override 118 | IDateTimeParser TimeZoneParser() { 119 | // TODO: implement TimeZoneParser 120 | throw UnimplementedError(); 121 | } 122 | 123 | @override 124 | IDateTimeUtilityConfiguration UtilityConfiguration() => EnglishDatetimeUtilityConfiguration(); 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/date_time/extract_result_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | 3 | class ExtractResultExtension { 4 | static List FilterAmbiguity( 5 | List extractResults, String text, Map ambiguityFiltersDict) { 6 | for (var regex in ambiguityFiltersDict.entries) { 7 | for (int i = extractResults.length - 1; i >= 0; i--) { 8 | var er = extractResults[i]; 9 | if (regex.key.hasMatch(er.text)) { 10 | var matches = regex.value.allMatches(text); 11 | if (matches.any((match) => match.start < er.start + er.length && match.end > er.start)) { 12 | extractResults.removeAt(i); 13 | } 14 | } 15 | } 16 | } 17 | 18 | return extractResults; 19 | } 20 | 21 | static List MergeAllResults(List res) { 22 | var ret = []; 23 | final results = [...res]; 24 | 25 | results.sort((a, b) => a.start - b.start); 26 | results.sort((a, b) => b.length - a.length); 27 | 28 | var mergedResults = []; 29 | for (var result in results) { 30 | if (result != null) { 31 | bool shouldAdd = true; 32 | var resStart = result.start; 33 | var resEnd = resStart + result.length; 34 | for (var index = 0; index < mergedResults.length && shouldAdd; index++) { 35 | var mergedStart = mergedResults[index].start; 36 | var mergedEnd = mergedStart + mergedResults[index].length; 37 | 38 | // It is included in one of the current results 39 | if (resStart >= mergedStart && resEnd <= mergedEnd) { 40 | shouldAdd = false; 41 | } 42 | 43 | // If it contains overlaps 44 | if (resStart > mergedStart && resStart < mergedEnd) { 45 | shouldAdd = false; 46 | } 47 | 48 | // It includes one of the results and should replace the included one 49 | if (resStart <= mergedStart && resEnd >= mergedEnd) { 50 | shouldAdd = false; 51 | mergedResults[index] = result; 52 | } 53 | } 54 | 55 | if (shouldAdd) { 56 | mergedResults.add(result); 57 | } 58 | } 59 | } 60 | 61 | return mergedResults; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/date_time/holiday_extractor_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_date_extractor.dart'; 2 | 3 | abstract interface class IHolidayExtractorConfiguration extends IDateTimeOptionsConfiguration { 4 | List holidayRegexes(); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/date_time/mod_and_date_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/base_date_parser.dart'; 2 | import 'package:nlp/src/date_time/constants.dart'; 3 | import 'package:nlp/src/duration/duration.dart'; 4 | 5 | class ModAndDateResult { 6 | static ModAndDateResult ModAndDateResultWithDateList( 7 | DateTime beginDate, DateTime endDate, String mod, List? dateList) { 8 | return ModAndDateResult(beginDate, endDate) 9 | ..Mod = mod 10 | ..DateList = dateList; 11 | } 12 | 13 | ModAndDateResult(DateTime beginDate, DateTime endDate) { 14 | this.BeginDate = beginDate; 15 | this.EndDate = endDate; 16 | this.Mod = ''; 17 | this.DateList = []; 18 | } 19 | 20 | late DateTime BeginDate; 21 | 22 | late DateTime EndDate; 23 | 24 | late String Mod; 25 | 26 | List? DateList; 27 | 28 | static ModAndDateResult GetModAndDate( 29 | DateTime beginDate, DateTime endDate, DateTime referenceDate, String timex, bool future) { 30 | DateTime beginDateResult = beginDate; 31 | DateTime endDateResult = endDate; 32 | var isBusinessDay = timex.endsWith(DateTimeConstants.TimexBusinessDay); 33 | var businessDayCount = 0; 34 | List? dateList = null; 35 | 36 | if (isBusinessDay) { 37 | businessDayCount = int.parse(timex.substring(1, timex.length - 3)); 38 | } 39 | 40 | if (future) { 41 | String mod = DateTimeConstants.AFTER_MOD; 42 | 43 | // For future the beginDate should add 1 first 44 | if (isBusinessDay) { 45 | beginDateResult = DurationParsingUtil.getNextBusinessDay(referenceDate); 46 | final nthBusinessDay = DurationParsingUtil.getNthBusinessDay(beginDateResult, businessDayCount - 1, true); 47 | dateList = nthBusinessDay.dateList; 48 | endDateResult = nthBusinessDay.result; 49 | endDateResult = endDateResult.AddDays(1); 50 | return ModAndDateResultWithDateList(beginDateResult, endDateResult, mod, dateList); 51 | } else { 52 | beginDateResult = referenceDate.AddDays(1); 53 | endDateResult = DurationParsingUtil.shiftDateTime(timex, beginDateResult, true); 54 | return ModAndDateResultWithDateList(beginDateResult, endDateResult, mod, null); 55 | } 56 | } else { 57 | const String mod = DateTimeConstants.BEFORE_MOD; 58 | 59 | if (isBusinessDay) { 60 | endDateResult = DurationParsingUtil.getNextBusinessDay(endDateResult, false); 61 | final nthBusinessDay = DurationParsingUtil.getNthBusinessDay(endDateResult, businessDayCount - 1, false); 62 | dateList = nthBusinessDay.dateList; 63 | beginDateResult = nthBusinessDay.result; 64 | endDateResult = endDateResult.AddDays(1); 65 | return ModAndDateResultWithDateList(beginDateResult, endDateResult, mod, dateList); 66 | } else { 67 | beginDateResult = DurationParsingUtil.shiftDateTime(timex, endDateResult, false); 68 | return ModAndDateResultWithDateList(beginDateResult, endDateResult, mod, null); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/date_time/resolution.dart: -------------------------------------------------------------------------------- 1 | class Resolution { 2 | List Values = []; 3 | } 4 | 5 | class Entry { 6 | String Timex = ''; 7 | 8 | String Type = ''; 9 | 10 | String Value = ''; 11 | 12 | String Start = ''; 13 | 14 | String End = ''; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/date_time/set_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/date_time/date_time_recognizer.dart'; 2 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 3 | 4 | class SetHandler { 5 | static (String, int) WeekDayGroupMatchTuple(NlpMatch match) { 6 | String weekday = match.getGroup("weekday").value; 7 | int del = 1; 8 | 9 | return (weekday, del); 10 | } 11 | 12 | static String WeekDayGroupMatchString(NlpMatch match) { 13 | String weekday = match.getGroup("weekday").value; 14 | 15 | return weekday; 16 | } 17 | 18 | static DateTimeResolutionResult ResolveSet(DateTimeResolutionResult result, String innerTimex) { 19 | result.timex = innerTimex; 20 | result.futureValue = result.pastValue = "Set: $innerTimex"; 21 | result.success = true; 22 | 23 | return result; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/date_time/time_of_day_resolution_result.dart: -------------------------------------------------------------------------------- 1 | class TimeOfDayResolutionResult { 2 | String Timex = ''; 3 | 4 | int BeginHour = 0; 5 | 6 | int EndHour = 0; 7 | 8 | int EndMin = 0; 9 | 10 | int Swift = 0; 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/date_time/time_period_functions.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | 3 | class TimePeriodFunctions { 4 | static List ApplyPotentialPeriodAmbiguityHotfix(String text, List timePeriodErs) { 5 | return timePeriodErs; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/date_time/timex_regex.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/nlp.dart'; 2 | 3 | class TimexRegex { 4 | static const String DateTimeCollectionName = "datetime"; 5 | static const String DateCollectionName = "date"; 6 | static const String TimeCollectionName = "time"; 7 | static const String PeriodCollectionName = "period"; 8 | 9 | static Map> timexRegex = >{ 10 | DateCollectionName: [ 11 | // date 12 | RegExp(r"^(XXXX|(?\d\d\d\d))-(?\d\d)(-(?\d\d))?"), 13 | RegExp(r"^XXXX-WXX-(?\d)"), 14 | RegExp(r"^XXXX-XX-(?\d\d)"), 15 | 16 | // daterange 17 | RegExp(r"^(?\d\d\d\d)"), 18 | RegExp(r"^(XXXX|(?\d\d\d\d))-(?\d\d)-W(?\d\d)"), 19 | RegExp(r"^(XXXX|(?\d\d\d\d))-(?\d\d)-WXX-(?\d{1,2})(-(?\d))?"), 20 | RegExp(r"^(?SP|SU|FA|WI)"), 21 | RegExp(r"^(XXXX|(?\d\d\d\d))-(?SP|SU|FA|WI)"), 22 | RegExp(r"^(XXXX|(?\d\d\d\d))-W(?\d\d)(-(?\d)|-(?WE))?"), 23 | ], 24 | TimeCollectionName: [ 25 | // time 26 | RegExp(r"T(?\d\d)Z?$"), 27 | RegExp(r"T(?\d\d):(?\d\d)Z?$"), 28 | RegExp(r"T(?\d\d):(?\d\d):(?\d\d)Z?$"), 29 | 30 | // timerange 31 | RegExp(r"^T(?DT|NI|MO|AF|EV)$"), 32 | ], 33 | PeriodCollectionName: [ 34 | RegExp(r"P(?\d*\.?\d+)(?Y|M|W|D|WE|WD)$"), 35 | RegExp(r"^PT(?\d*\.?\d+)H(\d*\.?\d+(M|S)){0,2}$"), 36 | RegExp(r"^PT(\d*\.?\d+H)?(?\d*\.?\d+)M(\d*\.?\d+S)?$"), 37 | RegExp(r"^PT(\d*\.?\d+(H|M)){0,2}(?\d*\.?\d+)S$"), 38 | ], 39 | }; 40 | 41 | static bool Extract(String name, String timex, Map result) { 42 | var lowerName = name.toLowerCase(); 43 | var nameGroup = ''; 44 | 45 | if (lowerName == DateTimeCollectionName) { 46 | nameGroup += DateCollectionName; 47 | nameGroup += TimeCollectionName; 48 | } else { 49 | nameGroup += lowerName; 50 | } 51 | 52 | var anyTrue = false; 53 | //for (var nameItem in nameGroup) 54 | for (int i = 0; i < nameGroup.length; i++) { 55 | var nameItem = nameGroup[i]; 56 | for (var entry in timexRegex[nameItem]!) { 57 | if (TryExtract(entry, timex, result)) { 58 | anyTrue = true; 59 | } 60 | } 61 | } 62 | 63 | return anyTrue; 64 | } 65 | 66 | static bool TryExtract(RegExp regex, String timex, Map result) { 67 | var regexResult = RegExpComposer.firstMatch(regex, timex); 68 | if (regexResult == null) { 69 | return false; 70 | } 71 | 72 | for (var groupName in regexResult.innerGroups.keys) { 73 | result[groupName] = regexResult.getGroup(groupName).value; 74 | } 75 | 76 | return true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/date_time/timezone_utility.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/string_matcher.dart'; 3 | import 'package:nlp/src/date_time/constants.dart'; 4 | import 'package:nlp/src/date_time/english_date_time.dart'; 5 | import 'package:nlp/src/duration/duration.dart'; 6 | import 'package:nlp/src/numbers/number_with_unit_tokenizer.dart'; 7 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 8 | 9 | class TimeZoneUtility { 10 | // private const RegexOptions RegexFlags = RegexOptions.Singleline | RegexOptions.ExplicitCapture; 11 | 12 | // private static readonly Regex BracketRegex = 13 | // new Regex(BaseDateTime.BracketRegex, RegexFlags, RegexTimeOut); 14 | 15 | static RegExp BracketRegex = RegExpComposer.sanitizeGroupsAndCompile(BaseDateTime.BracketRegex); 16 | 17 | // private static TimeSpan RegexTimeOut => DateTimeRecognizer.GetTimeout(MethodBase.GetCurrentMethod().DeclaringType); 18 | 19 | static List MergeTimeZones( 20 | List originalErs, List timeZoneErs, String text) { 21 | for (var er in originalErs) { 22 | for (var timeZoneEr in timeZoneErs) { 23 | // Extend timezone extraction to include brackets if any. 24 | var tzEr = ExtendTimeZoneExtraction(timeZoneEr, text); 25 | 26 | var begin = er.start + er.length; 27 | var end = tzEr.start; 28 | 29 | if (begin < end) { 30 | var gapText = text.substring(begin, end); 31 | 32 | if (gapText.trim().isEmpty) { 33 | var newLength = (tzEr.start + tzEr.length - er.start); 34 | 35 | er.text = text.substring(er.start, er.start + newLength); 36 | er.length = newLength; 37 | er.data = { 38 | DateTimeConstants.SYS_DATETIME_TIMEZONE: timeZoneEr, 39 | }; 40 | } 41 | } 42 | 43 | // Make sure timezone info propagates to longer span entity. 44 | if (er.isOverlap(timeZoneEr)) { 45 | er.data = { 46 | DateTimeConstants.SYS_DATETIME_TIMEZONE: timeZoneEr, 47 | }; 48 | } 49 | } 50 | } 51 | 52 | return originalErs; 53 | } 54 | 55 | static bool ShouldResolveTimeZone(ExtractResult er, DateTimeOptions options) { 56 | var enablePreview = options.match(DateTimeOptions.EnablePreview); 57 | if (!enablePreview) { 58 | return false; 59 | } 60 | 61 | var hasTimeZoneData = false; 62 | 63 | if (er.data is Map) { 64 | var metaData = er.data as Map; 65 | 66 | if (metaData.containsKey(DateTimeConstants.SYS_DATETIME_TIMEZONE)) { 67 | hasTimeZoneData = true; 68 | } 69 | } 70 | 71 | return hasTimeZoneData; 72 | } 73 | 74 | static StringMatcher BuildMatcherFromLists(List> collections) { 75 | StringMatcher matcher = StringMatcher(strategy: MatchStrategy.TrieTree, tokenizer: NumberWithUnitTokenizer()); 76 | List matcherList = []; 77 | 78 | for (List collection in collections) { 79 | for (final matcher in collection) { 80 | matcherList.add(matcher.trim().toLowerCase()); 81 | } 82 | } 83 | 84 | matcherList = matcherList.toSet().toList(); 85 | 86 | matcher.initFromValues(matcherList); 87 | 88 | return matcher; 89 | } 90 | 91 | static ExtractResult ExtendTimeZoneExtraction(ExtractResult timeZoneEr, String text) { 92 | var beforeStr = text.substring(0, timeZoneEr.start + 1); 93 | var afterStr = text.substring(timeZoneEr.start + timeZoneEr.length); 94 | var matchLeft = BracketRegex.firstMatch(beforeStr); 95 | var matchRight = BracketRegex.firstMatch(afterStr); 96 | if (matchLeft != null && matchRight != null) { 97 | timeZoneEr.start -= matchLeft.length; 98 | timeZoneEr.length += matchLeft.length + matchRight.length; 99 | } 100 | 101 | return timeZoneEr; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/src/duration/english_duration_parser_configuration.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/parser.dart'; 3 | import 'package:nlp/src/duration/base_duration_parser.dart'; 4 | import 'package:nlp/src/date_time/english_date_time_parser.dart'; 5 | import 'package:nlp/src/duration/duration.dart'; 6 | import 'package:nlp/src/duration/duration_extractor.dart'; 7 | 8 | class EnglishDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { 9 | EnglishDurationParserConfiguration( 10 | ICommonDateTimeParserConfiguration config, [ 11 | super.options = DateTimeOptions.None, 12 | super.dmyDateFormat = false, 13 | ]) { 14 | cardinalExtractor = config.cardinalExtractor; 15 | numberParser = config.numberParser; 16 | durationExtractor = DurationExtractor(config: EnglishDurationExtractorConfiguration(), merge: false); 17 | numberCombinedWithUnit = EnglishDurationExtractorConfiguration.NumberCombinedWithDurationUnit; 18 | 19 | anUnitRegex = EnglishDurationExtractorConfiguration.AnUnitRegex; 20 | duringRegex = EnglishDurationExtractorConfiguration.DuringRegex; 21 | allDateUnitRegex = EnglishDurationExtractorConfiguration.AllRegex; 22 | halfDateUnitRegex = EnglishDurationExtractorConfiguration.HalfRegex; 23 | suffixAndRegex = EnglishDurationExtractorConfiguration.SuffixAndRegex; 24 | followedUnit = EnglishDurationExtractorConfiguration.DurationFollowedUnit; 25 | conjunctionRegex = EnglishDurationExtractorConfiguration.ConjunctionRegex; 26 | inexactNumberRegex = EnglishDurationExtractorConfiguration.InexactNumberRegex; 27 | inexactNumberUnitRegex = EnglishDurationExtractorConfiguration.InexactNumberUnitRegex; 28 | durationUnitRegex = EnglishDurationExtractorConfiguration.DurationUnitRegex; 29 | 30 | unitMap = config.unitMap; 31 | unitValueMap = config.unitValueMap; 32 | doubleNumbers = config.doubleNumbers; 33 | } 34 | 35 | late final IExtractor cardinalExtractor; 36 | late final IExtractor durationExtractor; 37 | late final IParser numberParser; 38 | 39 | late final RegExp numberCombinedWithUnit; 40 | late final RegExp anUnitRegex; 41 | late final RegExp duringRegex; 42 | late final RegExp allDateUnitRegex; 43 | late final RegExp halfDateUnitRegex; 44 | late final RegExp suffixAndRegex; 45 | late final RegExp followedUnit; 46 | late final RegExp conjunctionRegex; 47 | late final RegExp inexactNumberRegex; 48 | late final RegExp inexactNumberUnitRegex; 49 | late final RegExp durationUnitRegex; 50 | 51 | late final Map unitMap; 52 | late final Map unitValueMap; 53 | late final Map doubleNumbers; 54 | 55 | @override 56 | IExtractor getCardinalExtractor() { 57 | return cardinalExtractor; 58 | } 59 | 60 | @override 61 | IExtractor getDurationExtractor() { 62 | return durationExtractor; 63 | } 64 | 65 | @override 66 | IParser? getNumberParser() { 67 | return numberParser; 68 | } 69 | 70 | @override 71 | RegExp getNumberCombinedWithUnit() { 72 | return numberCombinedWithUnit; 73 | } 74 | 75 | @override 76 | RegExp getAnUnitRegex() { 77 | return anUnitRegex; 78 | } 79 | 80 | @override 81 | RegExp getDuringRegex() { 82 | return duringRegex; 83 | } 84 | 85 | @override 86 | RegExp getAllDateUnitRegex() { 87 | return allDateUnitRegex; 88 | } 89 | 90 | @override 91 | RegExp getHalfDateUnitRegex() { 92 | return halfDateUnitRegex; 93 | } 94 | 95 | @override 96 | RegExp getSuffixAndRegex() { 97 | return suffixAndRegex; 98 | } 99 | 100 | @override 101 | RegExp getFollowedUnit() { 102 | return followedUnit; 103 | } 104 | 105 | @override 106 | RegExp getConjunctionRegex() { 107 | return conjunctionRegex; 108 | } 109 | 110 | @override 111 | RegExp getInexactNumberRegex() { 112 | return inexactNumberRegex; 113 | } 114 | 115 | @override 116 | RegExp getInexactNumberUnitRegex() { 117 | return inexactNumberUnitRegex; 118 | } 119 | 120 | @override 121 | RegExp getDurationUnitRegex() { 122 | return durationUnitRegex; 123 | } 124 | 125 | @override 126 | RegExp? getSpecialNumberUnitRegex() { 127 | return null; 128 | } 129 | 130 | @override 131 | Map getUnitMap() { 132 | return unitMap; 133 | } 134 | 135 | @override 136 | Map getUnitValueMap() { 137 | return unitValueMap; 138 | } 139 | 140 | @override 141 | Map getDoubleNumbers() { 142 | return doubleNumbers; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/src/numbers/number_constants.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | 3 | class Constants { 4 | static final SYS_NUM_CARDINAL = "builtin.num.cardinal"; 5 | static final SYS_NUM_DOUBLE = "builtin.num.double"; 6 | static final SYS_NUM_FRACTION = "builtin.num.fraction"; 7 | static final SYS_NUM_INTEGER = "builtin.num.integer"; 8 | static final SYS_NUM = "builtin.num"; 9 | static final SYS_NUM_ORDINAL = "builtin.num.ordinal"; 10 | static final SYS_NUM_PERCENTAGE = "builtin.num.percentage"; 11 | static final SYS_NUMRANGE = "builtin.num.numberrange"; 12 | static final SYS_NUMBER_ORDINAL = "builtin.num.ordinal"; 13 | 14 | // Model type name 15 | static final MODEL_NUMBER = "number"; 16 | static final MODEL_NUMBERRANGE = "numberrange"; 17 | static final MODEL_ORDINAL = "ordinal"; 18 | static final MODEL_PERCENTAGE = "percentage"; 19 | 20 | // NARROW NO-BREAK SPACE 21 | static final NO_BREAK_SPACE = '\u202f'; 22 | 23 | // Ordinal.relative attribute values 24 | static final RELATIVE_START = "start"; 25 | static final RELATIVE_END = "end"; 26 | static final RELATIVE_CURRENT = "current"; 27 | 28 | static final AGO_LABEL = "ago"; 29 | static final LATER_LABEL = "later"; 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/numbers/number_with_unit_tokenizer.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/extraction.dart'; 2 | import 'package:nlp/src/core/string_matcher.dart'; 3 | import 'package:nlp/src/core/string_utility.dart'; 4 | import 'package:nlp/src/regular_expressions/string_extensions.dart'; 5 | 6 | class NumberWithUnitTokenizer extends SimpleTokenizer { 7 | static final specialTokenCharacters = {'\$'}; 8 | 9 | /* The main difference between this strategy and SimpleTokenizer is for cases like 10 | * 'Bob's $ 100 cash'. 's' and '$' are independent tokens in SimpleTokenizer. 11 | * However, 's$' will return these two tokens too. Here, we let 's$' be a single 12 | * token to avoid the false positive. 13 | * Besides, letters and digits can't be mixed as a token. For cases like '200ml'. 14 | * '200ml' will be a token in SimpleTokenizer. Here, '200' and 'ml' are independent tokens. 15 | */ 16 | 17 | @override 18 | List tokenize(String? input) { 19 | final tokens = []; 20 | 21 | if (StringUtility.isNullOrEmpty(input)) { 22 | return tokens; 23 | } 24 | 25 | bool inToken = false; 26 | int tokenStart = 0; 27 | for (int i = 0; i < input!.length; i++) { 28 | final c = input[i]; 29 | if (c.isWhitespace) { 30 | if (inToken) { 31 | tokens.add(Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); 32 | inToken = false; 33 | } 34 | } else if ((!specialTokenCharacters.contains(c) && !c.isLetterOrDigit) || isChinese(c) || isJapanese(c)) { 35 | // Non-splittable currency units (as "$") are treated as regular letters. For instance, 'us$' should be a single token 36 | if (inToken) { 37 | tokens.add(Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); 38 | inToken = false; 39 | } 40 | 41 | tokens.add(Token(i, 1, input.substring(i, i + 1))); 42 | } else { 43 | if (inToken && i > 0) { 44 | final preChar = input[i - 1]; 45 | if (isSplittableUnit(c, preChar)) { 46 | // Split if letters or non-splittable units are adjacent with digits. 47 | tokens.add(Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); 48 | tokenStart = i; 49 | } 50 | } 51 | 52 | if (!inToken) { 53 | tokenStart = i; 54 | inToken = true; 55 | } 56 | } 57 | } 58 | 59 | if (inToken) { 60 | tokens.add(Token(tokenStart, input.length - tokenStart, input.substring(tokenStart, input.length))); 61 | } 62 | 63 | return tokens; 64 | } 65 | 66 | bool isSplittableUnit(String curChar, String preChar) { 67 | // To handle cases like '200ml', digits and letters cannot be mixed as a single token. '200ml' will be tokenized to '200' and 'ml'. 68 | if ((curChar.isLetter && preChar.isDigit) || (curChar.isDigit && preChar.isLetter)) { 69 | return true; 70 | } 71 | 72 | // Non-splittable currency units can't be mixed with digits. For example, '$100' or '100$' will be tokenized to '$' and '100', 73 | // '1$50' will be tokenized to '1', '$', and '50' 74 | if ((curChar.isDigit && specialTokenCharacters.contains(preChar)) || 75 | (specialTokenCharacters.contains(curChar) && preChar.isDigit)) { 76 | return true; 77 | } 78 | 79 | // Non-splittable currency units adjacent with letters are treated as regular token characters. For instance, 's$' or 'u$d' are single tokens. 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/regular_expressions/js_regexp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js' as js; 2 | 3 | class JsRegExp { 4 | /// Creates a JavaScript Regular Expression based in the given [pattern], 5 | /// with the given flags. 6 | /// 7 | /// [d] - `true` to enable indices for capture groups. 8 | JsRegExp( 9 | String pattern, { 10 | bool d = false, 11 | }) { 12 | _jsRegExp = js.context['RegExp'].apply([ 13 | pattern, 14 | if (d) 'd', 15 | ]); 16 | } 17 | 18 | late final js.JsObject _jsRegExp; 19 | 20 | JsRegExpResult? exec(String content) { 21 | final jsResult = _jsRegExp.callMethod('exec', [content]); 22 | return jsResult != null ? JsRegExpResult(jsResult) : null; 23 | } 24 | } 25 | 26 | // class JsRegExpMatch implements RegExpMatch { 27 | // JsRegExpMatch({ 28 | // required String target, 29 | // required this.pattern, 30 | // required Map captureGroups, 31 | // }) : _target = target, 32 | // _captureGroups = captureGroups { 33 | // groupNames = captureGroups.keys; 34 | // } 35 | // 36 | // final String _target; 37 | // 38 | // final Map _captureGroups; 39 | // 40 | // @override 41 | // final RegExp pattern; 42 | // 43 | // @override 44 | // late final Iterable groupNames; 45 | // 46 | // @override 47 | // String? namedGroup(String groupName) => _target.substring( 48 | // _captureGroups[groupName]!.$1, 49 | // _captureGroups[groupName]!.$2, 50 | // ); 51 | // 52 | // @override 53 | // String? operator [](int group) { 54 | // // TODO: implement [] 55 | // throw UnimplementedError(); 56 | // } 57 | // 58 | // @override 59 | // // TODO: implement end 60 | // int get end => throw UnimplementedError(); 61 | // 62 | // @override 63 | // String? group(int group) { 64 | // // TODO: implement group 65 | // throw UnimplementedError(); 66 | // } 67 | // 68 | // @override 69 | // // TODO: implement groupCount 70 | // int get groupCount => throw UnimplementedError(); 71 | // 72 | // @override 73 | // List groups(List groupIndices) { 74 | // // TODO: implement groups 75 | // throw UnimplementedError(); 76 | // } 77 | // 78 | // @override 79 | // // TODO: implement input 80 | // String get input => throw UnimplementedError(); 81 | // 82 | // @override 83 | // // TODO: implement start 84 | // int get start => throw UnimplementedError(); 85 | // } 86 | 87 | class JsRegExpResult { 88 | const JsRegExpResult(this._jsResult); 89 | 90 | final js.JsObject _jsResult; 91 | 92 | String get input => _jsResult['input']; 93 | 94 | int get index => _jsResult['index']; 95 | 96 | JsRegExpGroups get groups => JsRegExpGroups(_jsResult['groups']); 97 | 98 | /// Returns the name of each group in the RegExp that found a match within the [input]. 99 | Iterable get matchingGroupNames => groups.names.where((name) => getGroupBounds(name) != null); 100 | 101 | String? getGroup(String groupName) { 102 | final bounds = getGroupBounds(groupName); 103 | if (bounds == null) { 104 | return null; 105 | } 106 | 107 | return input.substring(bounds.$1, bounds.$2); 108 | } 109 | 110 | (int, int)? getGroupBounds(String groupName) { 111 | final js.JsObject namedGroupBounds = _jsResult['indices']['groups']; 112 | final boundsForGroup = namedGroupBounds[groupName]; 113 | 114 | if (boundsForGroup is js.JsArray && boundsForGroup.length == 2) { 115 | return (boundsForGroup[0], boundsForGroup[1]); 116 | } 117 | 118 | if (boundsForGroup is (int, int)) { 119 | return boundsForGroup; 120 | } 121 | 122 | return null; 123 | } 124 | 125 | List<(int, int)> get indices { 126 | final indices = <(int, int)>[]; 127 | 128 | final jsIndices = _jsResult['indices'] as js.JsArray; 129 | for (int i = 0; i < jsIndices.length; i += 1) { 130 | final range = jsIndices[i]; 131 | 132 | if (range is (int, int)) { 133 | indices.add((range.$1, range.$2)); 134 | continue; 135 | } 136 | 137 | if (range is js.JsArray) { 138 | if (range.length != 2) { 139 | print("WARNING: Found a match range that doesn't have 2 bounds: $range"); 140 | continue; 141 | } 142 | jsIndices.add((range[0], range[1])); 143 | continue; 144 | } 145 | 146 | print("WARNING: Expected a match range of type JsArray or (int, int) but it was: ${range.runtimeType}"); 147 | } 148 | 149 | return indices; 150 | } 151 | 152 | @override 153 | String toString() => '''JsRegExpResult[ 154 | - groups: $groups 155 | - indices: $indices 156 | ]'''; 157 | } 158 | 159 | class JsRegExpGroups { 160 | const JsRegExpGroups(this._jsGroups); 161 | 162 | final js.JsObject? _jsGroups; 163 | 164 | Iterable get names => _jsGroups?.keys.whereType() ?? []; 165 | 166 | String operator [](dynamic key) => _jsGroups?[key] ?? ''; 167 | 168 | @override 169 | String toString() => _jsGroups?.keys.join(", ") ?? ''; 170 | } 171 | 172 | extension on js.JsObject { 173 | List get keys => (js.context['Object'].callMethod('getOwnPropertyNames', [this]) as js.JsArray) 174 | .map((key) => key as String) 175 | .toList(); 176 | } 177 | 178 | void _jsExample() { 179 | final jsRegExp = js.context['RegExp'].apply(['^(?\\d+),(?.+)\$', 'd']); 180 | final result = jsRegExp.callMethod('exec', ["1560979912,Caroline"]); 181 | 182 | print("Result:"); 183 | print("$result"); 184 | print(""); 185 | 186 | print("Groups type: ${result['groups'].runtimeType}"); 187 | 188 | print("Timestamp group:"); 189 | print("${result['groups']['timestamp']}"); 190 | print(""); 191 | 192 | print("Author group:"); 193 | print("${result['groups']['author']}"); 194 | print(""); 195 | 196 | print("Indices:"); 197 | print("${result['indices']}"); 198 | print(""); 199 | } 200 | -------------------------------------------------------------------------------- /lib/src/regular_expressions/matching_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/src/core/string_matcher.dart'; 2 | import 'package:nlp/src/date_time/english_date_time.dart'; 3 | import 'package:nlp/src/regular_expressions/regular_expressions_extensions.dart'; 4 | 5 | class MatchingUtil { 6 | static RegExp invalidDayNumberPrefix = RegExp(BaseDateTime.InvalidDayNumberPrefix); 7 | 8 | static List> removeSubMatches(Iterable> matchResults) { 9 | return matchResults 10 | .where( 11 | (item) => matchResults 12 | .where( 13 | (ritem) => 14 | (ritem.start < item.start && (ritem.start + ritem.length) >= (item.start + item.length)) || 15 | (ritem.start <= item.start && (ritem.start + ritem.length) > (item.start + item.length)), 16 | ) 17 | .isNotEmpty, 18 | ) 19 | .toList(); 20 | 21 | // Original code 22 | // return StreamSupport.stream(matchResults.spliterator(), false) 23 | // .filter(item -> !StreamSupport.stream(matchResults.spliterator(), false) 24 | // .anyMatch(ritem -> (ritem.getStart() < item.getStart() && ritem.getEnd() >= item.getEnd()) || 25 | // (ritem.getStart() <= item.getStart() && ritem.getEnd() > item.getEnd()))) 26 | // .collect(Collectors.toCollection(ArrayList::new)); 27 | } 28 | 29 | static int? GetAgoLaterIndex(String text, RegExp regex, bool inSuffix) { 30 | var match = inSuffix ? regex.matchBegin(text, true) : regex.matchEnd(text, true); 31 | 32 | if (match != null) { 33 | return match.start + (inSuffix ? match.length : 0); 34 | } 35 | 36 | return null; 37 | } 38 | 39 | static int? GetTermIndex(String text, RegExp regex) { 40 | var match = regex.firstMatch(text.trim().split(' ').last); 41 | if (match != null) { 42 | return text.length - text.lastIndexOf(match.input.substring(match.start, match.end)); 43 | } 44 | 45 | return null; 46 | } 47 | 48 | static bool ContainsAgoLaterIndex(String text, RegExp regex, bool inSuffix) { 49 | return GetAgoLaterIndex(text, regex, inSuffix) != null; 50 | } 51 | 52 | static bool ContainsTermIndex(String text, RegExp regex) { 53 | return GetTermIndex(text, regex) != null; 54 | } 55 | 56 | static bool isInvalidDayNumberPrefix(String prefix) { 57 | return invalidDayNumberPrefix.hasMatch(prefix); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/regular_expressions/string_extensions.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | extension NlpString on String { 4 | bool get isLetterOrDigit => isLetter || isDigit; 5 | 6 | bool get isLetter { 7 | if (length != 1) { 8 | return false; 9 | } 10 | 11 | final codePoint = this[0].codeUnitAt(0); 12 | return (_unicode_A <= codePoint && codePoint <= _unicode_Z) || (_unicode_a <= codePoint && codePoint <= _unicode_z); 13 | } 14 | 15 | bool get isDigit { 16 | if (length != 1) { 17 | return false; 18 | } 19 | 20 | final codePoint = this[0].codeUnitAt(0); 21 | return codePoint ^ 0x30 <= 9; 22 | } 23 | 24 | bool get isWhitespace { 25 | if (length != 1) { 26 | return false; 27 | } 28 | 29 | final codePoint = this[0].codeUnitAt(0); 30 | return (codePoint >= 0x0009 && codePoint <= 0x000D) || 31 | codePoint == 0x0020 || 32 | codePoint == 0x0085 || 33 | codePoint == 0x00A0 || 34 | codePoint == 0x1680 || 35 | codePoint == 0x180E || 36 | (codePoint >= 0x2000 && codePoint <= 0x200A) || 37 | codePoint == 0x2028 || 38 | codePoint == 0x2029 || 39 | codePoint == 0x202F || 40 | codePoint == 0x205F || 41 | codePoint == 0x3000 || 42 | codePoint == 0xFEFF; 43 | } 44 | } 45 | 46 | const _unicode_A = 65; 47 | const _unicode_Z = 90; 48 | 49 | const _unicode_a = 97; 50 | const _unicode_z = 122; 51 | -------------------------------------------------------------------------------- /lib/src/time/english_time_zone_extractor.dart: -------------------------------------------------------------------------------- 1 | import 'package:diacritic/diacritic.dart'; 2 | import 'package:nlp/src/core/string_matcher.dart'; 3 | import 'package:nlp/src/duration/duration.dart'; 4 | import 'package:nlp/src/duration/duration_extractor.dart'; 5 | import 'package:nlp/src/numbers/number_with_unit_tokenizer.dart'; 6 | import 'package:nlp/src/time/english_time_zone.dart'; 7 | import 'package:nlp/src/time/time_zone_extractor.dart'; 8 | 9 | class EnglishTimeZoneExtractorConfiguration extends BaseOptionsConfiguration 10 | implements ITimeZoneExtractorConfiguration { 11 | // These regexes do need to be case insensitive for them to work correctly 12 | static final RegExp DirectUtcRegex = RegExp(EnglishTimeZone.DirectUtcRegex, caseSensitive: false); 13 | static final List AbbreviationsList = EnglishTimeZone.AbbreviationsList; 14 | static final List FullNameList = EnglishTimeZone.FullNameList; 15 | static final RegExp LocationTimeSuffixRegex = RegExp(EnglishTimeZone.LocationTimeSuffixRegex, caseSensitive: false); 16 | static final StringMatcher LocationMatcher = StringMatcher(); 17 | static final StringMatcher TimeZoneMatcher = buildMatcherFromLists([AbbreviationsList, FullNameList]); 18 | 19 | static final List AmbiguousTimezoneList = EnglishTimeZone.AmbiguousTimezoneList; 20 | 21 | EnglishTimeZoneExtractorConfiguration([super.options = DateTimeOptions.None]) { 22 | if (options.match(DateTimeOptions.EnablePreview)) { 23 | LocationMatcher.initFromValues( 24 | EnglishTimeZone.MajorLocations.map( 25 | (o) => removeDiacritics(o.toLowerCase()), 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | static StringMatcher buildMatcherFromLists(List> collections) { 32 | StringMatcher matcher = StringMatcher( 33 | strategy: MatchStrategy.TrieTree, 34 | tokenizer: NumberWithUnitTokenizer(), 35 | ); 36 | final matcherList = []; 37 | 38 | // Collect all lower case versions of given list items. 39 | final nonLowerCaseList = []; 40 | for (List collection in collections) { 41 | for (String item in collection) { 42 | matcherList.add(item.toLowerCase()); 43 | 44 | if (item != item.toLowerCase()) { 45 | nonLowerCaseList.add(item); 46 | } 47 | } 48 | } 49 | 50 | // For items that weren't originally lower case, add their original version. 51 | matcherList.addAll(nonLowerCaseList); 52 | 53 | matcher.initFromValues(matcherList); 54 | 55 | return matcher; 56 | } 57 | 58 | @override 59 | RegExp getDirectUtcRegex() { 60 | return DirectUtcRegex; 61 | } 62 | 63 | @override 64 | RegExp getLocationTimeSuffixRegex() { 65 | return LocationTimeSuffixRegex; 66 | } 67 | 68 | @override 69 | StringMatcher getLocationMatcher() { 70 | return LocationMatcher; 71 | } 72 | 73 | @override 74 | StringMatcher getTimeZoneMatcher() { 75 | return TimeZoneMatcher; 76 | } 77 | 78 | @override 79 | List getAmbiguousTimezoneList() { 80 | return AmbiguousTimezoneList; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/time/time_zone_extractor.dart: -------------------------------------------------------------------------------- 1 | import 'package:diacritic/diacritic.dart'; 2 | import 'package:nlp/nlp.dart'; 3 | import 'package:nlp/src/core/string_matcher.dart'; 4 | import 'package:nlp/src/date_time/date_time_extraction.dart'; 5 | import 'package:nlp/src/regular_expressions/matching_util.dart'; 6 | 7 | class BaseTimeZoneExtractor implements IDateTimeZoneExtractor { 8 | BaseTimeZoneExtractor(this._config); 9 | 10 | final ITimeZoneExtractorConfiguration _config; 11 | 12 | @override 13 | String getExtractorName() { 14 | return DateTimeConstants.SYS_DATETIME_TIMEZONE; 15 | } 16 | 17 | @override 18 | List extract(String input) { 19 | return extractTimeZone(input, DateTime.now()); 20 | } 21 | 22 | @override 23 | List extractDateTime(String input, DateTime reference) { 24 | return extractTimeZone(input, reference); 25 | } 26 | 27 | List extractTimeZone(String input, DateTime reference) { 28 | String normalizedText = removeDiacritics(input); 29 | final tokens = []; 30 | tokens.addAll(_matchTimeZones(normalizedText)); 31 | tokens.addAll(_matchLocationTimes(normalizedText, tokens)); 32 | return Token.mergeAllTokens(tokens, input, getExtractorName()); 33 | } 34 | 35 | @override 36 | List removeAmbiguousTimezone(List extractResults) { 37 | return extractResults 38 | .where( 39 | (result) => !_config.getAmbiguousTimezoneList().contains(result.text.toLowerCase()), 40 | ) 41 | .toList(); 42 | } 43 | 44 | List _matchTimeZones(String text) { 45 | final ret = []; 46 | 47 | // Direct UTC matches 48 | final directUtcMatches = RegExpComposer.getMatchesSimple(_config.getDirectUtcRegex(), text.toLowerCase()); 49 | // List directUtcMatches = RegExpUtility.getMatches(_config.getDirectUtcRegex(), text.toLowerCase()); 50 | if (directUtcMatches.isNotEmpty) { 51 | for (final match in directUtcMatches) { 52 | ret.add(Token(match.index, match.index + match.length)); 53 | } 54 | } 55 | 56 | Iterable> matches = _config.getTimeZoneMatcher().findByQueryText(text.toLowerCase()); 57 | for (MatchResult match in matches) { 58 | ret.add(Token(match.start, match.start + match.length)); 59 | } 60 | 61 | return ret; 62 | } 63 | 64 | List _matchLocationTimes(String text, List tokens) { 65 | final ret = []; 66 | 67 | if (_config.getLocationTimeSuffixRegex() == null) { 68 | return ret; 69 | } 70 | 71 | final timeMatch = RegExpComposer.getMatchesSimple(_config.getLocationTimeSuffixRegex()!, text); 72 | 73 | // Before calling a Find() in location matcher, check if all the matched suffixes by 74 | // LocationTimeSuffixRegex are already inside tokens extracted by TimeZone matcher. 75 | // If so, don't call the Find() as they have been extracted by TimeZone matcher, otherwise, call it. 76 | 77 | bool isAllSuffixInsideTokens = true; 78 | 79 | for (final match in timeMatch) { 80 | bool isInside = false; 81 | for (Token token in tokens) { 82 | if (token.start <= match.index && token.end >= match.index + match.length) { 83 | isInside = true; 84 | break; 85 | } 86 | } 87 | 88 | if (!isInside) { 89 | isAllSuffixInsideTokens = false; 90 | } 91 | 92 | if (!isAllSuffixInsideTokens) { 93 | break; 94 | } 95 | } 96 | 97 | if (timeMatch.isNotEmpty && !isAllSuffixInsideTokens) { 98 | int lastMatchIndex = timeMatch[timeMatch.length - 1].index; 99 | Iterable> matches = 100 | _config.getLocationMatcher().findByQueryText(text.substring(0, lastMatchIndex).toLowerCase()); 101 | List> locationMatches = MatchingUtil.removeSubMatches(matches); 102 | 103 | int i = 0; 104 | for (final match in timeMatch) { 105 | bool hasCityBefore = false; 106 | 107 | while (i < locationMatches.length && locationMatches[i].getEnd() <= match.index) { 108 | hasCityBefore = true; 109 | i++; 110 | 111 | if (i == locationMatches.length) { 112 | break; 113 | } 114 | } 115 | 116 | if (hasCityBefore && locationMatches[i - 1].getEnd() == match.index) { 117 | ret.add(Token(locationMatches[i - 1].start, match.index + match.length)); 118 | } 119 | 120 | if (i == locationMatches.length) { 121 | break; 122 | } 123 | } 124 | } 125 | 126 | return ret; 127 | } 128 | } 129 | 130 | abstract interface class IDateTimeZoneExtractor implements IDateTimeExtractor { 131 | List removeAmbiguousTimezone(List extractResults); 132 | } 133 | 134 | abstract interface class ITimeZoneExtractorConfiguration implements IOptionsConfiguration { 135 | RegExp getDirectUtcRegex(); 136 | 137 | RegExp? getLocationTimeSuffixRegex(); 138 | 139 | StringMatcher getLocationMatcher(); 140 | 141 | StringMatcher getTimeZoneMatcher(); 142 | 143 | List getAmbiguousTimezoneList(); 144 | } 145 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: nlp 2 | description: Natural language processing (NLP) for Dart 3 | version: 0.0.0 4 | repository: https://github.com/Flutter-Bounty-Hunters/nlp 5 | 6 | environment: 7 | sdk: ^3.0.0 8 | 9 | dependencies: 10 | diacritic: ^0.1.5 11 | intl: ^0.19.0 12 | js: ^0.6.3 13 | json_annotation: ^4.8.1 14 | mason_logger: ^0.2.10 15 | unorm_dart: ^0.3.0 16 | 17 | dev_dependencies: 18 | lints: ^2.1.0 19 | test: ^1.24.0 20 | 21 | build_runner: ^2.4.7 22 | json_serializable: ^6.7.1 23 | -------------------------------------------------------------------------------- /test/cases/english/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /test/cases/english/date_time_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | import '../test_case_serialization.dart'; 6 | 7 | void main() { 8 | final cases = parseTestCases(File("test/cases/english/date_time.json").readAsStringSync()); 9 | for (final testCase in cases) { 10 | test("Date and Time: '${testCase.input}'", () { 11 | // TODO: 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/cases/test_case_parsing_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:test/test.dart'; 5 | 6 | import 'test_case_serialization.dart'; 7 | 8 | void main() { 9 | test("Test cases can be deserialized", () { 10 | final file = File("test/cases/english/date_time.json"); 11 | expect(file.existsSync(), isTrue); 12 | 13 | final testCases = parseTestCases(file.readAsStringSync()); 14 | expect(testCases, isNotEmpty); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /test/infrastructure/regular_expression_composition_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:nlp/nlp.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group("Regular expression composition >", () { 6 | group("de-duplicates group names >", () { 7 | // Regular expressions are often composed from other lower-level regular expressions, 8 | // which can result in duplicated group names. For example, two smaller regular 9 | // expressions might both have groups named "". When composing those two regular 10 | // expressions to form a larger expression, those names need to be changed so that the 11 | // regular expression doesn't contain duplicate group names. 12 | 13 | test("number combined with unit", () { 14 | final deduplicatedPattern = EnglishDurationExtractorConfiguration().getNumberCombinedWithUnit().pattern; 15 | 16 | expect( 17 | deduplicatedPattern, 18 | r"\b(?\d+(\.\d*)?)(-)?(?(?(decade|year|(?month|week)|(?(business\s+|week\s*))?(?day)|fortnight|weekend)(?s)?|(?(^|\s)\d{1,4})[ymwd])\b|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\b", 19 | ); 20 | }); 21 | 22 | test("comparing regex", () { 23 | expect( 24 | r"((?<=\b)(?:((((?:seventeen|thirteen|fourteen|eighteen|nineteen|fifteen|sixteen|eleven|twelve|ten)|((?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)(\s+(and\s+)?|\s*-\s*)(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six))|(?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)|(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six)|(an?)(?=\s))(\s+(?:hundred|thousand|million|mln|billion|bln|trillion|tln|lakh|crore)s?)+)\s+(and\s+)?)*(?:(((?:seventeen|thirteen|fourteen|eighteen|nineteen|fifteen|sixteen|eleven|twelve|ten)|((?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)(\s+(and\s+)?|\s*-\s*)(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six))|(?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)|(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six))(\s+(?:hundred|thousand|million|mln|billion|bln|trillion|tln|lakh|crore)s?)*))|(((an?)(?=\s)(\s+(?:hundred|thousand|million|mln|billion|bln|trillion|tln|lakh|crore)s?)+)))(?=\b))", 25 | r"((?<=\b)(?:((((?:seventeen|thirteen|fourteen|eighteen|nineteen|fifteen|sixteen|eleven|twelve|ten)|((?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)(\s+(and\s+)?|\s*-\s*)(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six))|(?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)|(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six)|(an?)(?=\s))(\s+(?:hundred|thousand|million|mln|billion|bln|trillion|tln|lakh|crore)s?)+)\s+(and\s+)?)*(?:(((?:seventeen|thirteen|fourteen|eighteen|nineteen|fifteen|sixteen|eleven|twelve|ten)|((?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)(\s+(and\s+)?|\s*-\s*)(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six))|(?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)|(?:three|seven|eight|four|five|zero|n[ao]ught|nine|one|two|six))(\s+(?:hundred|thousand|million|mln|billion|bln|trillion|tln|lakh|crore)s?)*))|(((an?)(?=\s)(\s+(?:hundred|thousand|million|mln|billion|bln|trillion|tln|lakh|crore)s?)+)))(?=\b))", 26 | ); 27 | }); 28 | }); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------