blob: 85daa22ca099097735203f7429da497543cc1fcb [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef LIBTEXTCLASSIFIER_ANNOTATOR_TYPES_H_
#define LIBTEXTCLASSIFIER_ANNOTATOR_TYPES_H_
#include <time.h>
#include <algorithm>
#include <cmath>
#include <functional>
#include <map>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "annotator/entity-data_generated.h"
#include "annotator/knowledge/knowledge-engine-types.h"
#include "utils/base/integral_types.h"
#include "utils/base/logging.h"
#include "utils/flatbuffers/flatbuffers.h"
#include "utils/optional.h"
#include "utils/variant.h"
namespace libtextclassifier3 {
constexpr int kInvalidIndex = -1;
constexpr int kSunday = 1;
constexpr int kMonday = 2;
constexpr int kTuesday = 3;
constexpr int kWednesday = 4;
constexpr int kThursday = 5;
constexpr int kFriday = 6;
constexpr int kSaturday = 7;
// Index for a 0-based array of tokens.
using TokenIndex = int;
// Index for a 0-based array of codepoints.
using CodepointIndex = int;
// Marks a span in a sequence of codepoints. The first element is the index of
// the first codepoint of the span, and the second element is the index of the
// codepoint one past the end of the span.
struct CodepointSpan {
static const CodepointSpan kInvalid;
CodepointSpan() : first(kInvalidIndex), second(kInvalidIndex) {}
CodepointSpan(CodepointIndex start, CodepointIndex end)
: first(start), second(end) {}
CodepointSpan& operator=(const CodepointSpan& other) = default;
bool operator==(const CodepointSpan& other) const {
return this->first == other.first && this->second == other.second;
}
bool operator!=(const CodepointSpan& other) const {
return !(*this == other);
}
bool operator<(const CodepointSpan& other) const {
if (this->first != other.first) {
return this->first < other.first;
}
return this->second < other.second;
}
bool IsValid() const {
return this->first != kInvalidIndex && this->second != kInvalidIndex &&
this->first <= this->second && this->first >= 0;
}
bool IsEmpty() const { return this->first == this->second; }
CodepointIndex first;
CodepointIndex second;
};
// Pretty-printing function for CodepointSpan.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const CodepointSpan& span);
inline bool SpansOverlap(const CodepointSpan& a, const CodepointSpan& b) {
return a.first < b.second && b.first < a.second;
}
template <typename T>
bool DoesCandidateConflict(
const int considered_candidate, const std::vector<T>& candidates,
const std::set<int, std::function<bool(int, int)>>& chosen_indices_set) {
if (chosen_indices_set.empty()) {
return false;
}
auto conflicting_it = chosen_indices_set.lower_bound(considered_candidate);
// Check conflict on the right.
if (conflicting_it != chosen_indices_set.end() &&
SpansOverlap(candidates[considered_candidate].span,
candidates[*conflicting_it].span)) {
return true;
}
// Check conflict on the left.
// If we can't go more left, there can't be a conflict:
if (conflicting_it == chosen_indices_set.begin()) {
return false;
}
// Otherwise move one span left and insert if it doesn't overlap with the
// candidate.
--conflicting_it;
if (!SpansOverlap(candidates[considered_candidate].span,
candidates[*conflicting_it].span)) {
return false;
}
return true;
}
// Marks a span in a sequence of tokens. The first element is the index of the
// first token in the span, and the second element is the index of the token one
// past the end of the span.
struct TokenSpan {
static const TokenSpan kInvalid;
TokenSpan() : first(kInvalidIndex), second(kInvalidIndex) {}
TokenSpan(TokenIndex start, TokenIndex end) : first(start), second(end) {}
// Creates a token span consisting of one token.
explicit TokenSpan(int token_index)
: first(token_index), second(token_index + 1) {}
TokenSpan& operator=(const TokenSpan& other) = default;
bool operator==(const TokenSpan& other) const {
return this->first == other.first && this->second == other.second;
}
bool operator!=(const TokenSpan& other) const { return !(*this == other); }
bool operator<(const TokenSpan& other) const {
if (this->first != other.first) {
return this->first < other.first;
}
return this->second < other.second;
}
bool IsValid() const {
return this->first != kInvalidIndex && this->second != kInvalidIndex;
}
// Returns the size of the token span. Assumes that the span is valid.
int Size() const { return this->second - this->first; }
// Returns an expanded token span by adding a certain number of tokens on its
// left and on its right.
TokenSpan Expand(int num_tokens_left, int num_tokens_right) const {
return {this->first - num_tokens_left, this->second + num_tokens_right};
}
TokenIndex first;
TokenIndex second;
};
// Pretty-printing function for TokenSpan.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const TokenSpan& span);
// Returns an intersection of two token spans. Assumes that both spans are
// valid and overlapping.
inline TokenSpan IntersectTokenSpans(const TokenSpan& token_span1,
const TokenSpan& token_span2) {
return {std::max(token_span1.first, token_span2.first),
std::min(token_span1.second, token_span2.second)};
}
// Token holds a token, its position in the original string and whether it was
// part of the input span.
struct Token {
std::string value;
CodepointIndex start;
CodepointIndex end;
// Whether the token is a padding token.
bool is_padding;
// Whether the token contains only white characters.
bool is_whitespace;
// Default constructor constructs the padding-token.
Token()
: Token(/*arg_value=*/"", /*arg_start=*/kInvalidIndex,
/*arg_end=*/kInvalidIndex, /*is_padding=*/true,
/*is_whitespace=*/false) {}
Token(const std::string& arg_value, CodepointIndex arg_start,
CodepointIndex arg_end)
: Token(/*arg_value=*/arg_value, /*arg_start=*/arg_start,
/*arg_end=*/arg_end, /*is_padding=*/false,
/*is_whitespace=*/false) {}
Token(const std::string& arg_value, CodepointIndex arg_start,
CodepointIndex arg_end, bool is_padding, bool is_whitespace)
: value(arg_value),
start(arg_start),
end(arg_end),
is_padding(is_padding),
is_whitespace(is_whitespace) {}
bool operator==(const Token& other) const {
return value == other.value && start == other.start && end == other.end &&
is_padding == other.is_padding;
}
bool IsContainedInSpan(const CodepointSpan& span) const {
return start >= span.first && end <= span.second;
}
};
// Pretty-printing function for Token.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const Token& token);
// Returns a TokenSpan that merges all of the given token spans.
inline TokenSpan AllOf(const std::vector<Token>& tokens) {
return {0, static_cast<TokenIndex>(tokens.size())};
}
enum DatetimeGranularity {
GRANULARITY_UNKNOWN = -1, // GRANULARITY_UNKNOWN is used as a proxy for this
// structure being uninitialized.
GRANULARITY_YEAR = 0,
GRANULARITY_MONTH = 1,
GRANULARITY_WEEK = 2,
GRANULARITY_DAY = 3,
GRANULARITY_HOUR = 4,
GRANULARITY_MINUTE = 5,
GRANULARITY_SECOND = 6
};
// This struct represents a unit of date and time expression.
// Examples include:
// - In {March 21, 2019} datetime components are month: {March},
// day of month: {21} and year: {2019}.
// - {8:00 am} contains hour: {8}, minutes: {0} and am/pm: {am}
struct DatetimeComponent {
enum class ComponentType {
UNSPECIFIED = 0,
// Year of the date seen in the text match.
YEAR = 1,
// Month of the year starting with January = 1.
MONTH = 2,
// Week (7 days).
WEEK = 3,
// Day of week, start of the week is Sunday & its value is 1.
DAY_OF_WEEK = 4,
// Day of the month starting with 1.
DAY_OF_MONTH = 5,
// Hour of the day with a range of 0-23,
// values less than 12 need the AMPM field below or heuristics
// to definitively determine the time.
HOUR = 6,
// Minute of the hour with a range of 0-59.
MINUTE = 7,
// Seconds of the minute with a range of 0-59.
SECOND = 8,
// Meridiem field where 0 == AM, 1 == PM.
MERIDIEM = 9,
// Offset in number of minutes from UTC this date time is in.
ZONE_OFFSET = 10,
// Offset in number of hours for DST.
DST_OFFSET = 11,
};
// TODO(hassan): Remove RelativeQualifier as in the presence of relative
// count RelativeQualifier is redundant.
// Enum to represent the relative DateTimeComponent e.g. "next Monday",
// "the following day", "tomorrow".
enum class RelativeQualifier {
UNSPECIFIED = 0,
NEXT = 1,
THIS = 2,
LAST = 3,
NOW = 4,
TOMORROW = 5,
YESTERDAY = 6,
PAST = 7,
FUTURE = 8
};
bool operator==(const DatetimeComponent& other) const {
return component_type == other.component_type &&
relative_qualifier == other.relative_qualifier &&
relative_count == other.relative_count && value == other.value;
}
bool ShouldRoundToGranularity() const;
ComponentType component_type = ComponentType::UNSPECIFIED;
RelativeQualifier relative_qualifier = RelativeQualifier::UNSPECIFIED;
// Represents the absolute value of DateTime components.
int value = 0;
// The number of units of change present in the relative DateTimeComponent.
int relative_count = 0;
DatetimeComponent() = default;
explicit DatetimeComponent(ComponentType arg_component_type,
RelativeQualifier arg_relative_qualifier,
int arg_value, int arg_relative_count)
: component_type(arg_component_type),
relative_qualifier(arg_relative_qualifier),
value(arg_value),
relative_count(arg_relative_count) {}
};
// Utility method to calculate Returns the finest granularity of
// DatetimeComponents.
DatetimeGranularity GetFinestGranularity(
const std::vector<DatetimeComponent>& datetime_component);
// Return the 'DatetimeComponent' from collection filter by component type.
Optional<DatetimeComponent> GetDatetimeComponent(
const std::vector<DatetimeComponent>& datetime_components,
const DatetimeComponent::ComponentType& component_type);
struct DatetimeParseResult {
// The absolute time in milliseconds since the epoch in UTC.
int64 time_ms_utc;
// The precision of the estimate then in to calculating the milliseconds
DatetimeGranularity granularity;
// List of parsed DateTimeComponent.
std::vector<DatetimeComponent> datetime_components;
DatetimeParseResult() : time_ms_utc(0), granularity(GRANULARITY_UNKNOWN) {}
DatetimeParseResult(int64 arg_time_ms_utc,
DatetimeGranularity arg_granularity,
std::vector<DatetimeComponent> arg_datetime__components)
: time_ms_utc(arg_time_ms_utc),
granularity(arg_granularity),
datetime_components(arg_datetime__components) {}
bool IsSet() const { return granularity != GRANULARITY_UNKNOWN; }
bool operator==(const DatetimeParseResult& other) const {
return granularity == other.granularity &&
time_ms_utc == other.time_ms_utc &&
datetime_components == other.datetime_components;
}
};
const float kFloatCompareEpsilon = 1e-5;
struct DatetimeParseResultSpan {
CodepointSpan span;
std::vector<DatetimeParseResult> data;
float target_classification_score;
float priority_score;
DatetimeParseResultSpan()
: span(CodepointSpan::kInvalid),
target_classification_score(-1.0),
priority_score(-1.0) {}
DatetimeParseResultSpan(const CodepointSpan& span,
const std::vector<DatetimeParseResult>& data,
const float target_classification_score,
const float priority_score)
: span(span),
data(data),
target_classification_score(target_classification_score),
priority_score(priority_score) {}
bool operator==(const DatetimeParseResultSpan& other) const {
return span == other.span && data == other.data &&
std::abs(target_classification_score -
other.target_classification_score) < kFloatCompareEpsilon &&
std::abs(priority_score - other.priority_score) <
kFloatCompareEpsilon;
}
};
// Pretty-printing function for DatetimeParseResultSpan.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const DatetimeParseResultSpan& value);
// This struct contains information intended to uniquely identify a device
// contact. Instances are created by the Knowledge Engine, and dereferenced by
// the Contact Engine.
struct ContactPointer {
std::string focus_contact_id;
std::string device_id;
std::string device_contact_id;
std::string contact_name;
std::string contact_name_hash;
bool operator==(const ContactPointer& other) const {
return focus_contact_id == other.focus_contact_id &&
device_id == other.device_id &&
device_contact_id == other.device_contact_id &&
contact_name == other.contact_name &&
contact_name_hash == other.contact_name_hash;
}
};
struct ClassificationResult {
std::string collection;
float score;
DatetimeParseResult datetime_parse_result;
std::string serialized_knowledge_result;
ContactPointer contact_pointer;
std::string contact_name, contact_given_name, contact_family_name,
contact_nickname, contact_email_address, contact_phone_number,
contact_account_type, contact_account_name, contact_id;
std::string app_name, app_package_name;
int64 numeric_value;
double numeric_double_value;
// Length of the parsed duration in milliseconds.
int64 duration_ms;
// Internal score used for conflict resolution.
float priority_score;
// Entity data information.
std::string serialized_entity_data;
const EntityData* entity_data() const {
return LoadAndVerifyFlatbuffer<EntityData>(serialized_entity_data.data(),
serialized_entity_data.size());
}
explicit ClassificationResult()
: score(-1.0f),
numeric_value(0),
numeric_double_value(0.),
duration_ms(0),
priority_score(-1.0) {}
ClassificationResult(const std::string& arg_collection, float arg_score)
: collection(arg_collection),
score(arg_score),
numeric_value(0),
numeric_double_value(0.),
duration_ms(0),
priority_score(arg_score) {}
ClassificationResult(const std::string& arg_collection, float arg_score,
float arg_priority_score)
: collection(arg_collection),
score(arg_score),
numeric_value(0),
numeric_double_value(0.),
duration_ms(0),
priority_score(arg_priority_score) {}
bool operator!=(const ClassificationResult& other) const {
return !(*this == other);
}
bool operator==(const ClassificationResult& other) const;
};
// Aliases for long enum values.
const AnnotationUsecase ANNOTATION_USECASE_SMART =
AnnotationUsecase_ANNOTATION_USECASE_SMART;
const AnnotationUsecase ANNOTATION_USECASE_RAW =
AnnotationUsecase_ANNOTATION_USECASE_RAW;
struct LocationContext {
// User location latitude in degrees.
double user_location_lat = 180.;
// User location longitude in degrees.
double user_location_lng = 360.;
// The estimated horizontal accuracy of the user location in meters.
// Analogous to android.location.Location accuracy.
float user_location_accuracy_meters = 0.f;
bool operator==(const LocationContext& other) const {
return std::fabs(this->user_location_lat - other.user_location_lat) <
1e-8 &&
std::fabs(this->user_location_lng - other.user_location_lng) <
1e-8 &&
std::fabs(this->user_location_accuracy_meters -
other.user_location_accuracy_meters) < 1e-8;
}
};
struct BaseOptions {
// Comma-separated list of locale specification for the input text (BCP 47
// tags).
std::string locales;
// Comma-separated list of BCP 47 language tags.
std::string detected_text_language_tags;
// Tailors the output annotations according to the specified use-case.
AnnotationUsecase annotation_usecase = ANNOTATION_USECASE_SMART;
// The location context passed along with each annotation.
Optional<LocationContext> location_context;
// If true, the POD NER annotator is used.
bool use_pod_ner = true;
// If true and the model file supports that, the new vocab annotator is used
// to annotate "Dictionary". Otherwise, we use the FFModel to do so.
bool use_vocab_annotator = true;
bool operator==(const BaseOptions& other) const {
bool location_context_equality = this->location_context.has_value() ==
other.location_context.has_value();
if (this->location_context.has_value() &&
other.location_context.has_value()) {
location_context_equality =
this->location_context.value() == other.location_context.value();
}
return this->locales == other.locales &&
this->annotation_usecase == other.annotation_usecase &&
this->detected_text_language_tags ==
other.detected_text_language_tags &&
location_context_equality &&
this->use_pod_ner == other.use_pod_ner &&
this->use_vocab_annotator == other.use_vocab_annotator;
}
};
struct DatetimeOptions {
// For parsing relative datetimes, the reference now time against which the
// relative datetimes get resolved.
// UTC milliseconds since epoch.
int64 reference_time_ms_utc = 0;
// Timezone in which the input text was written (format as accepted by ICU).
std::string reference_timezone;
bool operator==(const DatetimeOptions& other) const {
return this->reference_time_ms_utc == other.reference_time_ms_utc &&
this->reference_timezone == other.reference_timezone;
}
};
struct SelectionOptions : public BaseOptions {};
struct ClassificationOptions : public BaseOptions, public DatetimeOptions {
// Comma-separated list of language tags which the user can read and
// understand (BCP 47).
std::string user_familiar_language_tags;
// If true, trigger dictionary on words that are of beginner level.
bool trigger_dictionary_on_beginner_words = false;
bool operator==(const ClassificationOptions& other) const {
return this->user_familiar_language_tags ==
other.user_familiar_language_tags &&
this->trigger_dictionary_on_beginner_words ==
other.trigger_dictionary_on_beginner_words &&
BaseOptions::operator==(other) && DatetimeOptions::operator==(other);
}
};
struct Permissions {
// If true the user location can be used to provide better annotations.
bool has_location_permission = true;
// If true, annotators can use personal data to provide personalized
// annotations.
bool has_personalization_permission = true;
bool operator==(const Permissions& other) const {
return this->has_location_permission == other.has_location_permission &&
this->has_personalization_permission ==
other.has_personalization_permission;
}
};
struct AnnotationOptions : public BaseOptions, public DatetimeOptions {
// List of entity types that should be used for annotation.
std::unordered_set<std::string> entity_types;
// If true, serialized_entity_data in the results is populated."
bool is_serialized_entity_data_enabled = false;
// Defines the permissions for the annotators.
Permissions permissions;
AnnotateMode annotate_mode = AnnotateMode::kEntityAnnotation;
// If true, trigger dictionary on words that are of beginner level.
bool trigger_dictionary_on_beginner_words = false;
bool operator==(const AnnotationOptions& other) const {
return this->is_serialized_entity_data_enabled ==
other.is_serialized_entity_data_enabled &&
this->permissions == other.permissions &&
this->entity_types == other.entity_types &&
this->annotate_mode == other.annotate_mode &&
this->trigger_dictionary_on_beginner_words ==
other.trigger_dictionary_on_beginner_words &&
BaseOptions::operator==(other) && DatetimeOptions::operator==(other);
}
};
// Returns true when ClassificationResults are euqal up to scores.
bool ClassificationResultsEqualIgnoringScoresAndSerializedEntityData(
const ClassificationResult& a, const ClassificationResult& b);
// Pretty-printing function for ClassificationResult.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const ClassificationResult& result);
// Pretty-printing function for std::vector<ClassificationResult>.
logging::LoggingStringStream& operator<<(
logging::LoggingStringStream& stream,
const std::vector<ClassificationResult>& results);
// Represents a result of Annotate call.
struct AnnotatedSpan {
enum class Source { OTHER, KNOWLEDGE, DURATION, DATETIME, PERSON_NAME };
// Unicode codepoint indices in the input string.
CodepointSpan span = CodepointSpan::kInvalid;
// Classification result for the span.
std::vector<ClassificationResult> classification;
// The source of the annotation, used in conflict resolution.
Source source = Source::OTHER;
AnnotatedSpan() = default;
AnnotatedSpan(CodepointSpan arg_span,
std::vector<ClassificationResult> arg_classification)
: span(arg_span), classification(std::move(arg_classification)) {}
AnnotatedSpan(CodepointSpan arg_span,
std::vector<ClassificationResult> arg_classification,
Source arg_source)
: span(arg_span),
classification(std::move(arg_classification)),
source(arg_source) {}
};
// Represents Annotations that correspond to all input fragments.
struct Annotations {
// List of annotations found in the corresponding input fragments. For these
// annotations, topicality score will not be set.
std::vector<std::vector<AnnotatedSpan>> annotated_spans;
// List of topicality results found across all input fragments.
std::vector<ClassificationResult> topicality_results;
Annotations() = default;
explicit Annotations(
std::vector<std::vector<AnnotatedSpan>> arg_annotated_spans)
: annotated_spans(std::move(arg_annotated_spans)) {}
Annotations(std::vector<std::vector<AnnotatedSpan>> arg_annotated_spans,
std::vector<ClassificationResult> arg_topicality_results)
: annotated_spans(std::move(arg_annotated_spans)),
topicality_results(std::move(arg_topicality_results)) {}
};
struct InputFragment {
std::string text;
float bounding_box_top;
float bounding_box_height;
// If present will override the AnnotationOptions reference time and timezone
// when annotating this specific string fragment.
Optional<DatetimeOptions> datetime_options;
};
// Pretty-printing function for AnnotatedSpan.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const AnnotatedSpan& span);
// StringPiece analogue for std::vector<T>.
template <class T>
class VectorSpan {
public:
VectorSpan() : begin_(), end_() {}
explicit VectorSpan(const std::vector<T>& v) // NOLINT(runtime/explicit)
: begin_(v.begin()), end_(v.end()) {}
VectorSpan(typename std::vector<T>::const_iterator begin,
typename std::vector<T>::const_iterator end)
: begin_(begin), end_(end) {}
const T& operator[](typename std::vector<T>::size_type i) const {
return *(begin_ + i);
}
int size() const { return end_ - begin_; }
typename std::vector<T>::const_iterator begin() const { return begin_; }
typename std::vector<T>::const_iterator end() const { return end_; }
const float* data() const { return &(*begin_); }
private:
typename std::vector<T>::const_iterator begin_;
typename std::vector<T>::const_iterator end_;
};
// Class to provide representation of date and time expressions
class DatetimeParsedData {
public:
// Function to set the absolute value of DateTimeComponent for the given
// FieldType, if the field is not present it will create the field and set
// the value.
void SetAbsoluteValue(const DatetimeComponent::ComponentType& field_type,
int value);
// Function to set the relative value of DateTimeComponent, if the field is
// not present the function will create the field and set the relative value.
void SetRelativeValue(
const DatetimeComponent::ComponentType& field_type,
const DatetimeComponent::RelativeQualifier& relative_value);
// Add collection of 'DatetimeComponent' to 'DatetimeParsedData'.
void AddDatetimeComponents(
const std::vector<DatetimeComponent>& datetime_components);
// Function to set the relative count of DateTimeComponent, if the field is
// not present the function will create the field and set the count.
void SetRelativeCount(const DatetimeComponent::ComponentType& field_type,
int relative_count);
// Function to populate the absolute value of the FieldType and return true.
// In case of no FieldType function will return false.
bool GetFieldValue(const DatetimeComponent::ComponentType& field_type,
int* field_value) const;
// Function to populate the relative value of the FieldType and return true.
// In case of no relative value function will return false.
bool GetRelativeValue(
const DatetimeComponent::ComponentType& field_type,
DatetimeComponent::RelativeQualifier* relative_value) const;
// Returns relative DateTimeComponent from the parsed DateTime span.
void GetRelativeDatetimeComponents(
std::vector<DatetimeComponent>* date_time_components) const;
// Returns DateTimeComponent from the parsed DateTime span.
void GetDatetimeComponents(
std::vector<DatetimeComponent>* date_time_components) const;
// Represent the granularity of the Parsed DateTime span. The function will
// return “GRANULARITY_UNKNOWN” if no datetime field is set.
DatetimeGranularity GetFinestGranularity() const;
// Utility function to check if DateTimeParsedData has FieldType initialized.
bool HasFieldType(const DatetimeComponent::ComponentType& field_type) const;
// Function to check if DateTimeParsedData has relative DateTimeComponent for
// given FieldType.
bool HasRelativeValue(
const DatetimeComponent::ComponentType& field_type) const;
// Function to check if DateTimeParsedData has absolute value
// DateTimeComponent for given FieldType.
bool HasAbsoluteValue(
const DatetimeComponent::ComponentType& field_type) const;
// Function to check if DateTimeParsedData has any DateTimeComponent.
bool IsEmpty() const;
private:
DatetimeComponent& GetOrCreateDatetimeComponent(
const DatetimeComponent::ComponentType& component_type);
std::map<DatetimeComponent::ComponentType, DatetimeComponent>
date_time_components_;
};
// Pretty-printing function for DateTimeParsedData.
logging::LoggingStringStream& operator<<(logging::LoggingStringStream& stream,
const DatetimeParsedData& data);
} // namespace libtextclassifier3
#endif // LIBTEXTCLASSIFIER_ANNOTATOR_TYPES_H_