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