1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/omnibox/browser/autocomplete_match.h"
7 #include "base/i18n/time_formatting.h"
8 #include "base/logging.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_piece.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "components/omnibox/browser/autocomplete_provider.h"
17 #include "components/omnibox/browser/suggestion_answer.h"
18 #include "components/search_engines/template_url.h"
19 #include "components/search_engines/template_url_service.h"
20 #include "components/url_formatter/url_formatter.h"
21 #include "grit/components_scaled_resources.h"
25 bool IsTrivialClassification(const ACMatchClassifications
& classifications
) {
26 return classifications
.empty() ||
27 ((classifications
.size() == 1) &&
28 (classifications
.back().style
== ACMatchClassification::NONE
));
31 // Returns true if one of the |terms_prefixed_by_http_or_https| matches the
32 // beginning of the URL (sans scheme). (Recall that
33 // |terms_prefixed_by_http_or_https|, for the input "http://a b" will be
34 // ["a"].) This suggests that the user wants a particular URL with a scheme
35 // in mind, hence the caller should not consider another URL like this one
36 // but with a different scheme to be a duplicate. |languages| is used to
37 // format punycoded URLs to decide if they match.
38 bool WordMatchesURLContent(
39 const std::vector
<base::string16
>& terms_prefixed_by_http_or_https
,
40 const std::string
& languages
,
42 size_t prefix_length
=
43 url
.scheme().length() + strlen(url::kStandardSchemeSeparator
);
44 DCHECK_GE(url
.spec().length(), prefix_length
);
45 const base::string16
& formatted_url
= url_formatter::FormatUrl(
46 url
, languages
, url_formatter::kFormatUrlOmitNothing
,
47 net::UnescapeRule::NORMAL
, nullptr, nullptr, &prefix_length
);
48 if (prefix_length
== base::string16::npos
)
50 const base::string16
& formatted_url_without_scheme
=
51 formatted_url
.substr(prefix_length
);
52 for (const auto& term
: terms_prefixed_by_http_or_https
) {
53 if (base::StartsWith(formatted_url_without_scheme
, term
,
54 base::CompareCase::SENSITIVE
))
62 // AutocompleteMatch ----------------------------------------------------------
65 const base::char16
AutocompleteMatch::kInvalidChars
[] = {
67 0x2028, // Line separator
68 0x2029, // Paragraph separator
72 AutocompleteMatch::AutocompleteMatch()
77 allowed_to_be_default_match(false),
78 swap_contents_and_description(false),
79 transition(ui::PAGE_TRANSITION_GENERATED
),
80 type(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
),
81 from_previous(false) {
84 AutocompleteMatch::AutocompleteMatch(AutocompleteProvider
* provider
,
92 allowed_to_be_default_match(false),
93 swap_contents_and_description(false),
94 transition(ui::PAGE_TRANSITION_TYPED
),
96 from_previous(false) {
99 AutocompleteMatch::AutocompleteMatch(const AutocompleteMatch
& match
)
100 : provider(match
.provider
),
101 relevance(match
.relevance
),
102 typed_count(match
.typed_count
),
103 deletable(match
.deletable
),
104 fill_into_edit(match
.fill_into_edit
),
105 inline_autocompletion(match
.inline_autocompletion
),
106 allowed_to_be_default_match(match
.allowed_to_be_default_match
),
107 destination_url(match
.destination_url
),
108 stripped_destination_url(match
.stripped_destination_url
),
109 contents(match
.contents
),
110 contents_class(match
.contents_class
),
111 description(match
.description
),
112 description_class(match
.description_class
),
113 swap_contents_and_description(match
.swap_contents_and_description
),
114 answer_contents(match
.answer_contents
),
115 answer_type(match
.answer_type
),
116 answer(SuggestionAnswer::copy(match
.answer
.get())),
117 transition(match
.transition
),
119 associated_keyword(match
.associated_keyword
.get() ?
120 new AutocompleteMatch(*match
.associated_keyword
) : NULL
),
121 keyword(match
.keyword
),
122 from_previous(match
.from_previous
),
123 search_terms_args(match
.search_terms_args
.get() ?
124 new TemplateURLRef::SearchTermsArgs(*match
.search_terms_args
) :
126 additional_info(match
.additional_info
),
127 duplicate_matches(match
.duplicate_matches
) {
130 AutocompleteMatch::~AutocompleteMatch() {
133 AutocompleteMatch
& AutocompleteMatch::operator=(
134 const AutocompleteMatch
& match
) {
138 provider
= match
.provider
;
139 relevance
= match
.relevance
;
140 typed_count
= match
.typed_count
;
141 deletable
= match
.deletable
;
142 fill_into_edit
= match
.fill_into_edit
;
143 inline_autocompletion
= match
.inline_autocompletion
;
144 allowed_to_be_default_match
= match
.allowed_to_be_default_match
;
145 destination_url
= match
.destination_url
;
146 stripped_destination_url
= match
.stripped_destination_url
;
147 contents
= match
.contents
;
148 contents_class
= match
.contents_class
;
149 description
= match
.description
;
150 description_class
= match
.description_class
;
151 swap_contents_and_description
= match
.swap_contents_and_description
;
152 answer_contents
= match
.answer_contents
;
153 answer_type
= match
.answer_type
;
154 answer
= SuggestionAnswer::copy(match
.answer
.get());
155 transition
= match
.transition
;
157 associated_keyword
.reset(match
.associated_keyword
.get() ?
158 new AutocompleteMatch(*match
.associated_keyword
) : NULL
);
159 keyword
= match
.keyword
;
160 from_previous
= match
.from_previous
;
161 search_terms_args
.reset(match
.search_terms_args
.get() ?
162 new TemplateURLRef::SearchTermsArgs(*match
.search_terms_args
) : NULL
);
163 additional_info
= match
.additional_info
;
164 duplicate_matches
= match
.duplicate_matches
;
169 int AutocompleteMatch::TypeToIcon(Type type
) {
171 static const int kIcons
[] = {
172 IDR_OMNIBOX_HTTP
, // URL_WHAT_YOU_TYPE
173 IDR_OMNIBOX_HTTP
, // HISTORY_URL
174 IDR_OMNIBOX_HTTP
, // HISTORY_TITLE
175 IDR_OMNIBOX_HTTP
, // HISTORY_BODY
176 IDR_OMNIBOX_HTTP
, // HISTORY_KEYWORD
177 IDR_OMNIBOX_HTTP
, // NAVSUGGEST
178 IDR_OMNIBOX_SEARCH
, // SEARCH_WHAT_YOU_TYPED
179 IDR_OMNIBOX_SEARCH
, // SEARCH_HISTORY
180 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST
181 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_ENTITY
182 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_TAIL
183 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_PERSONALIZED
184 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_PROFILE
185 IDR_OMNIBOX_SEARCH
, // SEARCH_OTHER_ENGINE
186 IDR_OMNIBOX_EXTENSION_APP
, // EXTENSION_APP
187 IDR_OMNIBOX_SEARCH
, // CONTACT_DEPRECATED
188 IDR_OMNIBOX_HTTP
, // BOOKMARK_TITLE
189 IDR_OMNIBOX_HTTP
, // NAVSUGGEST_PERSONALIZED
190 IDR_OMNIBOX_CALCULATOR
, // CALCULATOR
191 IDR_OMNIBOX_HTTP
, // CLIPBOARD
194 static const int kIcons
[] = {
195 IDR_OMNIBOX_HTTP
, // URL_WHAT_YOU_TYPE
196 IDR_OMNIBOX_HISTORY
, // HISTORY_URL
197 IDR_OMNIBOX_HISTORY
, // HISTORY_TITLE
198 IDR_OMNIBOX_HISTORY
, // HISTORY_BODY
199 IDR_OMNIBOX_HISTORY
, // HISTORY_KEYWORD
200 IDR_OMNIBOX_HTTP
, // NAVSUGGEST
201 IDR_OMNIBOX_SEARCH
, // SEARCH_WHAT_YOU_TYPED
202 IDR_OMNIBOX_HISTORY
, // SEARCH_HISTORY
203 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST
204 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_ENTITY
205 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_TAIL
206 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_PERSONALIZED
207 IDR_OMNIBOX_SEARCH
, // SEARCH_SUGGEST_PROFILE
208 IDR_OMNIBOX_SEARCH
, // SEARCH_OTHER_ENGINE
209 IDR_OMNIBOX_EXTENSION_APP
, // EXTENSION_APP
210 IDR_OMNIBOX_SEARCH
, // CONTACT_DEPRECATED
211 IDR_OMNIBOX_HTTP
, // BOOKMARK_TITLE
212 IDR_OMNIBOX_HTTP
, // NAVSUGGEST_PERSONALIZED
213 IDR_OMNIBOX_CALCULATOR
, // CALCULATOR
214 IDR_OMNIBOX_HTTP
, // CLIPBOARD
217 static_assert(arraysize(kIcons
) == AutocompleteMatchType::NUM_TYPES
,
218 "icons array must have NUM_TYPES elements");
223 bool AutocompleteMatch::MoreRelevant(const AutocompleteMatch
& elem1
,
224 const AutocompleteMatch
& elem2
) {
225 // For equal-relevance matches, we sort alphabetically, so that providers
226 // who return multiple elements at the same priority get a "stable" sort
227 // across multiple updates.
228 return (elem1
.relevance
== elem2
.relevance
) ?
229 (elem1
.contents
< elem2
.contents
) : (elem1
.relevance
> elem2
.relevance
);
233 bool AutocompleteMatch::DestinationsEqual(const AutocompleteMatch
& elem1
,
234 const AutocompleteMatch
& elem2
) {
235 if (elem1
.stripped_destination_url
.is_empty() &&
236 elem2
.stripped_destination_url
.is_empty())
238 return elem1
.stripped_destination_url
== elem2
.stripped_destination_url
;
242 void AutocompleteMatch::ClassifyMatchInString(
243 const base::string16
& find_text
,
244 const base::string16
& text
,
246 ACMatchClassifications
* classification
) {
247 ClassifyLocationInString(text
.find(find_text
), find_text
.length(),
248 text
.length(), style
, classification
);
252 void AutocompleteMatch::ClassifyLocationInString(
253 size_t match_location
,
255 size_t overall_length
,
257 ACMatchClassifications
* classification
) {
258 classification
->clear();
260 // Don't classify anything about an empty string
261 // (AutocompleteMatch::Validate() checks this).
262 if (overall_length
== 0)
265 // Mark pre-match portion of string (if any).
266 if (match_location
!= 0) {
267 classification
->push_back(ACMatchClassification(0, style
));
270 // Mark matching portion of string.
271 if (match_location
== base::string16::npos
) {
272 // No match, above classification will suffice for whole string.
275 // Classifying an empty match makes no sense and will lead to validation
277 DCHECK_GT(match_length
, 0U);
278 classification
->push_back(ACMatchClassification(match_location
,
279 (style
| ACMatchClassification::MATCH
) & ~ACMatchClassification::DIM
));
281 // Mark post-match portion of string (if any).
282 const size_t after_match(match_location
+ match_length
);
283 if (after_match
< overall_length
) {
284 classification
->push_back(ACMatchClassification(after_match
, style
));
289 AutocompleteMatch::ACMatchClassifications
290 AutocompleteMatch::MergeClassifications(
291 const ACMatchClassifications
& classifications1
,
292 const ACMatchClassifications
& classifications2
) {
293 // We must return the empty vector only if both inputs are truly empty.
294 // The result of merging an empty vector with a single (0, NONE)
295 // classification is the latter one-entry vector.
296 if (IsTrivialClassification(classifications1
))
297 return classifications2
.empty() ? classifications1
: classifications2
;
298 if (IsTrivialClassification(classifications2
))
299 return classifications1
;
301 ACMatchClassifications output
;
302 for (ACMatchClassifications::const_iterator i
= classifications1
.begin(),
303 j
= classifications2
.begin(); i
!= classifications1
.end();) {
304 AutocompleteMatch::AddLastClassificationIfNecessary(&output
,
305 std::max(i
->offset
, j
->offset
), i
->style
| j
->style
);
306 const size_t next_i_offset
= (i
+ 1) == classifications1
.end() ?
307 static_cast<size_t>(-1) : (i
+ 1)->offset
;
308 const size_t next_j_offset
= (j
+ 1) == classifications2
.end() ?
309 static_cast<size_t>(-1) : (j
+ 1)->offset
;
310 if (next_i_offset
>= next_j_offset
)
312 if (next_j_offset
>= next_i_offset
)
320 std::string
AutocompleteMatch::ClassificationsToString(
321 const ACMatchClassifications
& classifications
) {
322 std::string serialized_classifications
;
323 for (size_t i
= 0; i
< classifications
.size(); ++i
) {
325 serialized_classifications
+= ',';
326 serialized_classifications
+= base::IntToString(classifications
[i
].offset
) +
327 ',' + base::IntToString(classifications
[i
].style
);
329 return serialized_classifications
;
333 ACMatchClassifications
AutocompleteMatch::ClassificationsFromString(
334 const std::string
& serialized_classifications
) {
335 ACMatchClassifications classifications
;
336 std::vector
<base::StringPiece
> tokens
= base::SplitStringPiece(
337 serialized_classifications
, ",", base::KEEP_WHITESPACE
,
338 base::SPLIT_WANT_NONEMPTY
);
339 DCHECK(!(tokens
.size() & 1)); // The number of tokens should be even.
340 for (size_t i
= 0; i
< tokens
.size(); i
+= 2) {
341 int classification_offset
= 0;
342 int classification_style
= ACMatchClassification::NONE
;
343 if (!base::StringToInt(tokens
[i
], &classification_offset
) ||
344 !base::StringToInt(tokens
[i
+ 1], &classification_style
)) {
346 return classifications
;
348 classifications
.push_back(ACMatchClassification(classification_offset
,
349 classification_style
));
351 return classifications
;
355 void AutocompleteMatch::AddLastClassificationIfNecessary(
356 ACMatchClassifications
* classifications
,
359 DCHECK(classifications
);
360 if (classifications
->empty() || classifications
->back().style
!= style
) {
361 DCHECK(classifications
->empty() ||
362 (offset
> classifications
->back().offset
));
363 classifications
->push_back(ACMatchClassification(offset
, style
));
368 bool AutocompleteMatch::HasMatchStyle(
369 const ACMatchClassifications
& classifications
) {
370 for (const auto& it
: classifications
) {
371 if (it
.style
& AutocompleteMatch::ACMatchClassification::MATCH
)
378 base::string16
AutocompleteMatch::SanitizeString(const base::string16
& text
) {
379 // NOTE: This logic is mirrored by |sanitizeString()| in
380 // omnibox_custom_bindings.js.
381 base::string16 result
;
382 base::TrimWhitespace(text
, base::TRIM_LEADING
, &result
);
383 base::RemoveChars(result
, kInvalidChars
, &result
);
388 bool AutocompleteMatch::IsSearchType(Type type
) {
389 return type
== AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
||
390 type
== AutocompleteMatchType::SEARCH_HISTORY
||
391 type
== AutocompleteMatchType::SEARCH_SUGGEST
||
392 type
== AutocompleteMatchType::SEARCH_OTHER_ENGINE
||
393 type
== AutocompleteMatchType::CALCULATOR
||
394 IsSpecializedSearchType(type
);
398 bool AutocompleteMatch::IsSpecializedSearchType(Type type
) {
399 return type
== AutocompleteMatchType::SEARCH_SUGGEST_ENTITY
||
400 type
== AutocompleteMatchType::SEARCH_SUGGEST_TAIL
||
401 type
== AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED
||
402 type
== AutocompleteMatchType::SEARCH_SUGGEST_PROFILE
;
406 TemplateURL
* AutocompleteMatch::GetTemplateURLWithKeyword(
407 TemplateURLService
* template_url_service
,
408 const base::string16
& keyword
,
409 const std::string
& host
) {
410 if (template_url_service
== NULL
)
412 TemplateURL
* template_url
= keyword
.empty() ?
413 NULL
: template_url_service
->GetTemplateURLForKeyword(keyword
);
414 return (template_url
|| host
.empty()) ?
415 template_url
: template_url_service
->GetTemplateURLForHost(host
);
419 GURL
AutocompleteMatch::GURLToStrippedGURL(
421 const AutocompleteInput
& input
,
422 const std::string
& languages
,
423 TemplateURLService
* template_url_service
,
424 const base::string16
& keyword
) {
428 GURL stripped_destination_url
= url
;
430 // If the destination URL looks like it was generated from a TemplateURL,
431 // remove all substitutions other than the search terms. This allows us
432 // to eliminate cases like past search URLs from history that differ only
433 // by some obscure query param from each other or from the search/keyword
435 TemplateURL
* template_url
= GetTemplateURLWithKeyword(
436 template_url_service
, keyword
, stripped_destination_url
.host());
437 if (template_url
!= NULL
&&
438 template_url
->SupportsReplacement(
439 template_url_service
->search_terms_data())) {
440 base::string16 search_terms
;
441 if (template_url
->ExtractSearchTermsFromURL(
442 stripped_destination_url
,
443 template_url_service
->search_terms_data(),
445 stripped_destination_url
=
446 GURL(template_url
->url_ref().ReplaceSearchTerms(
447 TemplateURLRef::SearchTermsArgs(search_terms
),
448 template_url_service
->search_terms_data()));
452 // |replacements| keeps all the substitions we're going to make to
453 // from {destination_url} to {stripped_destination_url}. |need_replacement|
454 // is a helper variable that helps us keep track of whether we need
455 // to apply the replacement.
456 bool needs_replacement
= false;
457 GURL::Replacements replacements
;
459 // Remove the www. prefix from the host.
460 static const char prefix
[] = "www.";
461 static const size_t prefix_len
= arraysize(prefix
) - 1;
462 std::string host
= stripped_destination_url
.host();
463 if (host
.compare(0, prefix_len
, prefix
) == 0) {
464 replacements
.SetHostStr(base::StringPiece(host
).substr(prefix_len
));
465 needs_replacement
= true;
468 // Remove any trailing slash (if it's not a lone slash), or add a slash (to
469 // make a lone slash) if the path is empty. (We can't unconditionally
470 // remove even lone slashes because for some schemes the path must consist
471 // of at least a slash.)
472 const std::string
& path
= stripped_destination_url
.path();
473 if ((path
.length() > 1) && (path
[path
.length() - 1] == '/')) {
474 replacements
.SetPathStr(
475 base::StringPiece(path
).substr(0, path
.length() - 1));
476 needs_replacement
= true;
477 } else if (path
.empty()) {
478 static const char slash
[] = "/";
479 replacements
.SetPathStr(base::StringPiece(slash
));
480 needs_replacement
= true;
483 // Replace https protocol with http, as long as the user didn't explicitly
484 // specify one of the two.
485 if (stripped_destination_url
.SchemeIs(url::kHttpsScheme
) &&
486 (input
.terms_prefixed_by_http_or_https().empty() ||
487 !WordMatchesURLContent(
488 input
.terms_prefixed_by_http_or_https(), languages
, url
))) {
489 replacements
.SetScheme(url::kHttpScheme
,
490 url::Component(0, strlen(url::kHttpScheme
)));
491 needs_replacement
= true;
494 if (needs_replacement
)
495 stripped_destination_url
= stripped_destination_url
.ReplaceComponents(
497 return stripped_destination_url
;
500 void AutocompleteMatch::ComputeStrippedDestinationURL(
501 const AutocompleteInput
& input
,
502 const std::string
& languages
,
503 TemplateURLService
* template_url_service
) {
504 stripped_destination_url
= GURLToStrippedGURL(
505 destination_url
, input
, languages
, template_url_service
, keyword
);
508 void AutocompleteMatch::EnsureUWYTIsAllowedToBeDefault(
509 const AutocompleteInput
& input
,
510 const std::string
& languages
,
511 TemplateURLService
* template_url_service
) {
512 if (!allowed_to_be_default_match
) {
513 const GURL
& stripped_canonical_input_url
=
514 AutocompleteMatch::GURLToStrippedGURL(
515 input
.canonicalized_url(), input
, languages
, template_url_service
,
517 ComputeStrippedDestinationURL(input
, languages
, template_url_service
);
518 allowed_to_be_default_match
=
519 stripped_canonical_input_url
== stripped_destination_url
;
523 void AutocompleteMatch::GetKeywordUIState(
524 TemplateURLService
* template_url_service
,
525 base::string16
* keyword
,
526 bool* is_keyword_hint
) const {
527 *is_keyword_hint
= associated_keyword
.get() != NULL
;
528 keyword
->assign(*is_keyword_hint
? associated_keyword
->keyword
:
529 GetSubstitutingExplicitlyInvokedKeyword(template_url_service
));
532 base::string16
AutocompleteMatch::GetSubstitutingExplicitlyInvokedKeyword(
533 TemplateURLService
* template_url_service
) const {
534 if (transition
!= ui::PAGE_TRANSITION_KEYWORD
||
535 template_url_service
== NULL
) {
536 return base::string16();
539 const TemplateURL
* t_url
= GetTemplateURL(template_url_service
, false);
541 t_url
->SupportsReplacement(
542 template_url_service
->search_terms_data())) ?
543 keyword
: base::string16();
546 TemplateURL
* AutocompleteMatch::GetTemplateURL(
547 TemplateURLService
* template_url_service
,
548 bool allow_fallback_to_destination_host
) const {
549 return GetTemplateURLWithKeyword(
550 template_url_service
, keyword
,
551 allow_fallback_to_destination_host
?
552 destination_url
.host() : std::string());
555 void AutocompleteMatch::RecordAdditionalInfo(const std::string
& property
,
556 const std::string
& value
) {
557 DCHECK(!property
.empty());
558 DCHECK(!value
.empty());
559 additional_info
[property
] = value
;
562 void AutocompleteMatch::RecordAdditionalInfo(const std::string
& property
,
564 RecordAdditionalInfo(property
, base::IntToString(value
));
567 void AutocompleteMatch::RecordAdditionalInfo(const std::string
& property
,
568 const base::Time
& value
) {
569 RecordAdditionalInfo(property
,
571 base::TimeFormatShortDateAndTime(value
)));
574 std::string
AutocompleteMatch::GetAdditionalInfo(
575 const std::string
& property
) const {
576 AdditionalInfo::const_iterator
i(additional_info
.find(property
));
577 return (i
== additional_info
.end()) ? std::string() : i
->second
;
580 bool AutocompleteMatch::IsVerbatimType() const {
581 const bool is_keyword_verbatim_match
=
582 (type
== AutocompleteMatchType::SEARCH_OTHER_ENGINE
&&
584 provider
->type() == AutocompleteProvider::TYPE_SEARCH
);
585 return type
== AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
||
586 type
== AutocompleteMatchType::URL_WHAT_YOU_TYPED
||
587 is_keyword_verbatim_match
;
590 bool AutocompleteMatch::SupportsDeletion() const {
594 for (ACMatches::const_iterator
it(duplicate_matches
.begin());
595 it
!= duplicate_matches
.end(); ++it
) {
602 void AutocompleteMatch::PossiblySwapContentsAndDescriptionForDisplay() {
603 if (swap_contents_and_description
) {
604 std::swap(contents
, description
);
605 std::swap(contents_class
, description_class
);
610 void AutocompleteMatch::Validate() const {
611 ValidateClassifications(contents
, contents_class
);
612 ValidateClassifications(description
, description_class
);
615 void AutocompleteMatch::ValidateClassifications(
616 const base::string16
& text
,
617 const ACMatchClassifications
& classifications
) const {
619 DCHECK(classifications
.empty());
623 // The classifications should always cover the whole string.
624 DCHECK(!classifications
.empty()) << "No classification for \"" << text
<< '"';
625 DCHECK_EQ(0U, classifications
[0].offset
)
626 << "Classification misses beginning for \"" << text
<< '"';
627 if (classifications
.size() == 1)
630 // The classifications should always be sorted.
631 size_t last_offset
= classifications
[0].offset
;
632 for (ACMatchClassifications::const_iterator
i(classifications
.begin() + 1);
633 i
!= classifications
.end(); ++i
) {
634 const char* provider_name
= provider
? provider
->GetName() : "None";
635 DCHECK_GT(i
->offset
, last_offset
)
636 << " Classification for \"" << text
<< "\" with offset of " << i
->offset
637 << " is unsorted in relation to last offset of " << last_offset
638 << ". Provider: " << provider_name
<< ".";
639 DCHECK_LT(i
->offset
, text
.length())
640 << " Classification of [" << i
->offset
<< "," << text
.length()
641 << "] is out of bounds for \"" << text
<< "\". Provider: "
642 << provider_name
<< ".";
643 last_offset
= i
->offset
;