Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Side by Side Diff: chrome/browser/history/query_parser.cc

Issue 9316129: Don't strip punctuation inside quotes in history search queries. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: New patch based on chat with mrossetti. Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/history/query_parser.h" 5 #include "chrome/browser/history/query_parser.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/compiler_specific.h" 9 #include "base/compiler_specific.h"
10 #include "base/i18n/break_iterator.h" 10 #include "base/i18n/break_iterator.h"
11 #include "base/i18n/case_conversion.h" 11 #include "base/i18n/case_conversion.h"
12 #include "base/logging.h" 12 #include "base/logging.h"
13 #include "base/stl_util.h" 13 #include "base/stl_util.h"
14 #include "base/string_util.h"
14 15
15 namespace { 16 namespace {
16 17
17 // Returns true if |mp1.first| is less than |mp2.first|. This is used to 18 // Returns true if |mp1.first| is less than |mp2.first|. This is used to
18 // sort match positions. 19 // sort match positions.
19 int CompareMatchPosition(const Snippet::MatchPosition& mp1, 20 int CompareMatchPosition(const Snippet::MatchPosition& mp1,
20 const Snippet::MatchPosition& mp2) { 21 const Snippet::MatchPosition& mp2) {
21 return mp1.first < mp2.first; 22 return mp1.first < mp2.first;
22 } 23 }
23 24
(...skipping 15 matching lines...) Expand all
39 mp.second = i->second; 40 mp.second = i->second;
40 i = matches->erase(i); 41 i = matches->erase(i);
41 } else { 42 } else {
42 return; 43 return;
43 } 44 }
44 } 45 }
45 } 46 }
46 47
47 // Sorts the match positions in |matches| by their first index, then coalesces 48 // Sorts the match positions in |matches| by their first index, then coalesces
48 // any match positions that intersect each other. 49 // any match positions that intersect each other.
49 void CoalseAndSortMatchPositions(Snippet::MatchPositions* matches) { 50 void CoalesceAndSortMatchPositions(Snippet::MatchPositions* matches) {
50 std::sort(matches->begin(), matches->end(), &CompareMatchPosition); 51 std::sort(matches->begin(), matches->end(), &CompareMatchPosition);
51 // WARNING: we don't use iterator here as CoalesceMatchesFrom may remove 52 // WARNING: we don't use iterator here as CoalesceMatchesFrom may remove
52 // from matches. 53 // from matches.
53 for (size_t i = 0; i < matches->size(); ++i) 54 for (size_t i = 0; i < matches->size(); ++i)
54 CoalesceMatchesFrom(i, matches); 55 CoalesceMatchesFrom(i, matches);
55 } 56 }
56 57
57 // Returns true if the character is considered a quote. 58 // Returns true if the character is considered a quote.
58 bool IsQueryQuote(wchar_t ch) { 59 bool IsQueryQuote(wchar_t ch) {
59 return ch == '"' || 60 return ch == '"' ||
60 ch == 0xab || // left pointing double angle bracket 61 ch == 0xab || // left pointing double angle bracket
61 ch == 0xbb || // right pointing double angle bracket 62 ch == 0xbb || // right pointing double angle bracket
62 ch == 0x201c || // left double quotation mark 63 ch == 0x201c || // left double quotation mark
63 ch == 0x201d || // right double quotation mark 64 ch == 0x201d || // right double quotation mark
64 ch == 0x201e; // double low-9 quotation mark 65 ch == 0x201e; // double low-9 quotation mark
65 } 66 }
66 67
68 // Returns true if the character is considered non-breaking when it appears in
69 // the middle of a word. This can be used to prevent an URL-like query from
70 // being broken into multiple words.
71 bool IsNonBreakingSymbol(wchar_t ch) {
72 return ch == '.' ||
73 ch == '-' ||
74 ch == '_' ||
75 ch == '/' ||
76 ch == '~';
77 }
78
67 } // namespace 79 } // namespace
68 80
69 // Inheritance structure: 81 // Inheritance structure:
70 // Queries are represented as trees of QueryNodes. 82 // Queries are represented as trees of QueryNodes.
71 // QueryNodes are either a collection of subnodes (a QueryNodeList) 83 // QueryNodes are either a collection of subnodes (a QueryNodeList)
72 // or a single word (a QueryNodeWord). 84 // or a single word (a QueryNodeWord).
73 85
74 // A QueryNodeWord is a single word in the query. 86 // A QueryNodeWord is a single word in the query.
sky 2012/02/08 23:31:28 Update description.
Patrick Dubroy 2012/02/09 14:47:00 Done.
75 class QueryNodeWord : public QueryNode { 87 class QueryNodeWord : public QueryNode {
76 public: 88 public:
77 explicit QueryNodeWord(const string16& word); 89 explicit QueryNodeWord(const string16& word);
78 virtual ~QueryNodeWord(); 90 virtual ~QueryNodeWord();
79 91
80 const string16& word() const { return word_; } 92 const string16& word() const { return word_; }
81 93
82 void set_literal(bool literal) { literal_ = literal; } 94 void set_literal(bool literal) { literal_ = literal; }
83 95
84 // QueryNode: 96 // QueryNode:
85 virtual int AppendToSQLiteQuery(string16* query) const OVERRIDE; 97 virtual int AppendToSQLiteQuery(string16* query) const OVERRIDE;
86 virtual bool IsWord() const OVERRIDE; 98 virtual bool IsWord() const OVERRIDE;
87 virtual bool Matches(const string16& word, bool exact) const OVERRIDE; 99 virtual bool Matches(const string16& word, bool exact) const OVERRIDE;
88 virtual bool HasMatchIn( 100 virtual bool HasMatchIn(
89 const std::vector<QueryWord>& words, 101 const std::vector<QueryWord>& words,
90 Snippet::MatchPositions* match_positions) const OVERRIDE; 102 Snippet::MatchPositions* match_positions) const OVERRIDE;
91 virtual void AppendWords(std::vector<string16>* words) const OVERRIDE; 103 virtual void AppendWords(std::vector<string16>* words) const OVERRIDE;
104 virtual void Append(const string16& word);
sky 2012/02/08 23:31:28 This isn't part of QueryNode, so move it some wher
Patrick Dubroy 2012/02/09 14:47:00 Whoops, yeah. Done.
92 105
93 private: 106 private:
94 string16 word_; 107 string16 word_;
95 bool literal_; 108 bool literal_;
96 109
97 DISALLOW_COPY_AND_ASSIGN(QueryNodeWord); 110 DISALLOW_COPY_AND_ASSIGN(QueryNodeWord);
98 }; 111 };
99 112
100 QueryNodeWord::QueryNodeWord(const string16& word) 113 QueryNodeWord::QueryNodeWord(const string16& word)
101 : word_(word), 114 : word_(word),
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
134 return true; 147 return true;
135 } 148 }
136 } 149 }
137 return false; 150 return false;
138 } 151 }
139 152
140 void QueryNodeWord::AppendWords(std::vector<string16>* words) const { 153 void QueryNodeWord::AppendWords(std::vector<string16>* words) const {
141 words->push_back(word_); 154 words->push_back(word_);
142 } 155 }
143 156
157 void QueryNodeWord::Append(const string16& str) {
158 word_ += str;
159 }
160
144 // A QueryNodeList has a collection of QueryNodes which are deleted in the end. 161 // A QueryNodeList has a collection of QueryNodes which are deleted in the end.
145 class QueryNodeList : public QueryNode { 162 class QueryNodeList : public QueryNode {
146 public: 163 public:
147 typedef std::vector<QueryNode*> QueryNodeVector; 164 typedef std::vector<QueryNode*> QueryNodeVector;
148 165
149 QueryNodeList(); 166 QueryNodeList();
150 virtual ~QueryNodeList(); 167 virtual ~QueryNodeList();
151 168
152 QueryNodeVector* children() { return &children_; } 169 QueryNodeVector* children() { return &children_; }
153 170
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
340 if (!query_nodes[i]->HasMatchIn(query_words, &matches)) 357 if (!query_nodes[i]->HasMatchIn(query_words, &matches))
341 return false; 358 return false;
342 } 359 }
343 if (lower_text.length() != text.length()) { 360 if (lower_text.length() != text.length()) {
344 // The lower case string differs from the original string. The matches are 361 // The lower case string differs from the original string. The matches are
345 // meaningless. 362 // meaningless.
346 // TODO(sky): we need a better way to align the positions so that we don't 363 // TODO(sky): we need a better way to align the positions so that we don't
347 // completely punt here. 364 // completely punt here.
348 match_positions->clear(); 365 match_positions->clear();
349 } else { 366 } else {
350 CoalseAndSortMatchPositions(&matches); 367 CoalesceAndSortMatchPositions(&matches);
351 match_positions->swap(matches); 368 match_positions->swap(matches);
352 } 369 }
353 return true; 370 return true;
354 } 371 }
355 372
356 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) { 373 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) {
357 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD); 374 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD);
358 // TODO(evanm): support a locale here 375 // TODO(evanm): support a locale here
359 if (!iter.Init()) 376 if (!iter.Init())
360 return false; 377 return false;
361 378
362 // To handle nesting, we maintain a stack of QueryNodeLists. 379 // To handle nesting, we maintain a stack of QueryNodeLists.
363 // The last element (back) of the stack contains the current, deepest node. 380 // The last element (back) of the stack contains the current, deepest node.
364 std::vector<QueryNodeList*> query_stack; 381 std::vector<QueryNodeList*> query_stack;
365 query_stack.push_back(root); 382 query_stack.push_back(root);
366 383
367 bool in_quotes = false; // whether we're currently in a quoted phrase 384 bool in_quotes = false; // whether we're currently in a quoted phrase
385 QueryNodeWord* current_word = NULL;
368 while (iter.Advance()) { 386 while (iter.Advance()) {
369 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It 387 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It
370 // is not necessarily a word, but could also be a sequence of punctuation 388 // is not necessarily a word, it could also be a punctuation or whitespace
371 // or whitespace. 389 // character. Punctuation is preserved inside quotes, and otherwise removed
372 if (iter.IsWord()) { 390 // except if it is a non-breaking character in the middle of a word.
373 QueryNodeWord* word_node = new QueryNodeWord(iter.GetString()); 391
374 if (in_quotes) 392 wchar_t last_char = query[iter.prev()];
375 word_node->set_literal(true); 393 if (IsQueryQuote(last_char)) {
376 query_stack.back()->AddChild(word_node); 394 if (!in_quotes) {
377 } else { // Punctuation. 395 QueryNodeList* quotes_node = new QueryNodePhrase;
378 if (IsQueryQuote(query[iter.prev()])) { 396 query_stack.back()->AddChild(quotes_node);
379 if (!in_quotes) { 397 query_stack.push_back(quotes_node);
380 QueryNodeList* quotes_node = new QueryNodePhrase; 398 in_quotes = true;
381 query_stack.back()->AddChild(quotes_node); 399 } else {
382 query_stack.push_back(quotes_node); 400 query_stack.pop_back(); // Stop adding to the quoted phrase.
383 in_quotes = true; 401 in_quotes = false;
384 } else {
385 query_stack.pop_back(); // Stop adding to the quoted phrase.
386 in_quotes = false;
387 }
388 } 402 }
403 current_word = NULL;
404 } else if (iter.IsWord() || (in_quotes && !IsWhitespace(last_char))) {
405 // Append to the current word if the new token is a word or a non-
406 // whitespace character inside quotes.
407 if (current_word) {
408 current_word->Append(iter.GetString());
409 } else {
410 current_word = new QueryNodeWord(iter.GetString());
411 current_word->set_literal(in_quotes);
412 query_stack.back()->AddChild(current_word);
413 }
414 } else if (current_word != NULL) {
415 // Allow non-breaking symbols inside a word.
416 // Any other punctuation or whitespace character ends the current word.
417 if (IsNonBreakingSymbol(last_char))
418 current_word->Append(iter.GetString());
419 else
420 current_word = NULL;
389 } 421 }
390 } 422 }
391 423
392 root->RemoveEmptySubnodes(); 424 root->RemoveEmptySubnodes();
393 return true; 425 return true;
394 } 426 }
395 427
396 void QueryParser::ExtractQueryWords(const string16& text, 428 void QueryParser::ExtractQueryWords(const string16& text,
397 std::vector<QueryWord>* words) { 429 std::vector<QueryWord>* words) {
398 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); 430 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD);
399 // TODO(evanm): support a locale here 431 // TODO(evanm): support a locale here
400 if (!iter.Init()) 432 if (!iter.Init())
401 return; 433 return;
402 434
403 while (iter.Advance()) { 435 while (iter.Advance()) {
404 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It 436 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It
405 // is not necessarily a word, but could also be a sequence of punctuation 437 // is not necessarily a word, but could also be a sequence of punctuation
406 // or whitespace. 438 // or whitespace.
407 if (iter.IsWord()) { 439 if (iter.IsWord()) {
408 string16 word = iter.GetString(); 440 string16 word = iter.GetString();
409 if (!word.empty()) { 441 if (!word.empty()) {
410 words->push_back(QueryWord()); 442 words->push_back(QueryWord());
411 words->back().word = word; 443 words->back().word = word;
412 words->back().position = iter.prev(); 444 words->back().position = iter.prev();
413 } 445 }
414 } 446 }
415 } 447 }
416 } 448 }
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/history/query_parser_unittest.cc » ('j') | chrome/browser/history/query_parser_unittest.cc » ('J')

Powered by Google App Engine
This is Rietveld 408576698