1 //
2 // Copyright RIME Developers
3 // Distributed under the BSD License
4 //
5 // 2012-01-03 GONG Chen <chen.sst@gmail.com>
6 //
7 #include <boost/algorithm/string.hpp>
8 #include <rime/candidate.h>
9 #include <rime/engine.h>
10 #include <rime/schema.h>
11 #include <rime/segmentation.h>
12 #include <rime/ticket.h>
13 #include <rime/translation.h>
14 #include <rime/algo/syllabifier.h>
15 #include <rime/dict/dictionary.h>
16 #include <rime/dict/reverse_lookup_dictionary.h>
17 #include <rime/gear/reverse_lookup_translator.h>
18 #include <rime/gear/translator_commons.h>
19 #include <rime/gear/table_translator.h>
20 
21 
22 //static const char* quote_left = "\xef\xbc\x88";
23 //static const char* quote_right = "\xef\xbc\x89";
24 //static const char* separator = "\xef\xbc\x8c";
25 
26 namespace rime {
27 
28 class ReverseLookupTranslation : public TableTranslation {
29  public:
ReverseLookupTranslation(ReverseLookupDictionary * dict,TranslatorOptions * options,const string & input,size_t start,size_t end,const string & preedit,DictEntryIterator && iter,bool quality)30   ReverseLookupTranslation(ReverseLookupDictionary* dict,
31                            TranslatorOptions* options,
32                            const string& input,
33                            size_t start, size_t end,
34                            const string& preedit,
35                            DictEntryIterator&& iter,
36                            bool quality)
37       : TableTranslation(
38             options, NULL, input, start, end, preedit, std::move(iter)),
39         dict_(dict), options_(options), quality_(quality) {
40   }
41   virtual an<Candidate> Peek();
42   virtual int Compare(an<Translation> other,
43                       const CandidateList& candidates);
44  protected:
45   ReverseLookupDictionary* dict_;
46   TranslatorOptions* options_;
47   bool quality_;
48 };
49 
Peek()50 an<Candidate> ReverseLookupTranslation::Peek() {
51   if (exhausted())
52     return nullptr;
53   const auto& entry(iter_.Peek());
54   string tips;
55   if (dict_) {
56     dict_->ReverseLookup(entry->text, &tips);
57     if (options_) {
58       options_->comment_formatter().Apply(&tips);
59     }
60     //if (!tips.empty()) {
61     //  boost::algorithm::replace_all(tips, " ", separator);
62     //}
63   }
64   an<Candidate> cand = New<SimpleCandidate>(
65       "reverse_lookup",
66       start_,
67       end_,
68       entry->text,
69       !tips.empty() ? tips : entry->comment,
70       preedit_);
71   return cand;
72 }
73 
Compare(an<Translation> other,const CandidateList & candidates)74 int ReverseLookupTranslation::Compare(an<Translation> other,
75                                       const CandidateList& candidates) {
76   if (!other || other->exhausted())
77     return -1;
78   if (exhausted())
79     return 1;
80   auto theirs = other->Peek();
81   if (!theirs)
82     return -1;
83   if (quality_ && theirs->type() == "completion")
84     return -1;
85   if (theirs->type() == "sentence")
86     return -1;
87   return 1;
88 }
89 
ReverseLookupTranslator(const Ticket & ticket)90 ReverseLookupTranslator::ReverseLookupTranslator(const Ticket& ticket)
91     : Translator(ticket), tag_("reverse_lookup") {
92   if (ticket.name_space == "translator") {
93     name_space_ = "reverse_lookup";
94   }
95   if (!ticket.schema)
96     return;
97   Config* config = ticket.schema->config();
98   config->GetString(name_space_ + "/tag", &tag_);
99 }
100 
Initialize()101 void ReverseLookupTranslator::Initialize() {
102   initialized_ = true;  // no retry
103   if (!engine_)
104     return;
105   Ticket ticket(engine_, name_space_);
106   options_.reset(new TranslatorOptions(ticket));
107   Config* config = engine_->schema()->config();
108   if (!config)
109     return;
110   config->GetString(name_space_ + "/prefix", &prefix_);
111   config->GetString(name_space_ + "/suffix", &suffix_);
112   config->GetString(name_space_ + "/tips", &tips_);
113   {
114     bool enabled = false;
115     if (!config->GetBool(name_space_ + "/enable_completion", &enabled))
116       options_->set_enable_completion(false);  // overridden default
117   }
118 
119   if (auto component = Dictionary::Require("dictionary")) {
120     dict_.reset(component->Create(ticket));
121   }
122   if (dict_) {
123     dict_->Load();
124   }
125   else {
126     return;
127   }
128   auto rev_component =
129       ReverseLookupDictionary::Require("reverse_lookup_dictionary");
130   if (!rev_component)
131     return;
132   // lookup target defaults to "translator/dictionary"
133   string rev_target("translator");
134   config->GetString(name_space_ + "/target", &rev_target);
135   Ticket rev_ticket(engine_, rev_target);
136   rev_dict_.reset(rev_component->Create(rev_ticket));
137   if (rev_dict_) {
138     rev_dict_->Load();
139   }
140 }
141 
Query(const string & input,const Segment & segment)142 an<Translation> ReverseLookupTranslator::Query(const string& input,
143                                                        const Segment& segment) {
144   if (!segment.HasTag(tag_))
145     return nullptr;
146   if (!initialized_)
147     Initialize();  // load reverse dict at first use
148   if (!dict_ || !dict_->loaded())
149     return nullptr;
150   DLOG(INFO) << "input = '" << input
151              << "', [" << segment.start << ", " << segment.end << ")";
152 
153   const string& preedit(input);
154 
155   size_t start = 0;
156   if (!prefix_.empty() && boost::starts_with(input, prefix_)) {
157     start = prefix_.length();
158   }
159   string code = input.substr(start);
160   if (!suffix_.empty() && boost::ends_with(code, suffix_)) {
161     code.resize(code.length() - suffix_.length());
162   }
163 
164   if (start > 0) {
165     // usually translators do not modify the segment directly;
166     // prompt text is best set by a processor or a segmentor.
167     const_cast<Segment*>(&segment)->prompt = tips_;
168   }
169 
170   DictEntryIterator iter;
171   bool quality = false;
172   if (start < input.length()) {
173     if (options_ && options_->enable_completion()) {
174       dict_->LookupWords(&iter, code, true, 100);
175       quality = !iter.exhausted() &&
176                 (iter.Peek()->remaining_code_length == 0);
177     }
178     else {
179       // 2012-04-08 gongchen: fetch multi-syllable words from rev-lookup table
180       SyllableGraph graph;
181       Syllabifier syllabifier("", true, options_->strict_spelling());
182       size_t consumed = syllabifier.BuildSyllableGraph(code,
183                                                        *dict_->prism(),
184                                                        &graph);
185       if (consumed == code.length()) {
186         auto collector = dict_->Lookup(graph, 0);
187         if (collector && !collector->empty() &&
188             collector->rbegin()->first == consumed) {
189           iter = std::move(collector->rbegin()->second);
190           quality = !graph.vertices.empty() &&
191               (graph.vertices.rbegin()->second == kNormalSpelling);
192         }
193       }
194     }
195   }
196   if (!iter.exhausted()) {
197     return Cached<ReverseLookupTranslation>(rev_dict_.get(),
198                                             options_.get(),
199                                             code,
200                                             segment.start,
201                                             segment.end,
202                                             preedit,
203                                             std::move(iter),
204                                             quality);
205   }
206   return nullptr;
207 }
208 
209 }  // namespace rime
210