1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  *  SPDX-FileCopyrightText: 2005 Takuro Ashie
4  *  SPDX-FileCopyrightText: 2012 CSSlayer <wengxt@gmail.com>
5  *
6  *  SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include <fcitx-utils/log.h>
10 #include <fstream>
11 
12 #include "key2kana_table.h"
13 #include "style_file.h"
14 #include <fcitx-utils/charutils.h>
15 
16 namespace {
17 const int MAX_LINE_LENGTH = 4096;
18 
escape(const std::string & str)19 std::string escape(const std::string &str) {
20     std::string dest = str;
21 
22     for (unsigned int i = 0; i < dest.size(); i++) {
23         if (dest[i] == '#' ||                   // for comment
24             dest[i] == '\\' ||                  // for backslash itself
25             dest[i] == '=' ||                   // for separatort
26             dest[i] == '[' || dest[i] == ']' || // for section
27             dest[i] == ',' ||                   // for array
28             dest[i] == ' ' || dest[i] == '\t')  // for space
29         {
30             dest.insert(i, "\\");
31             i++;
32         }
33     }
34 
35     return dest;
36 }
37 
unescape(const std::string & str)38 std::string unescape(const std::string &str) {
39     std::string dest = str;
40 
41     for (unsigned int i = 0; i < dest.size(); i++) {
42         if (dest[i] == '\\') {
43             dest.erase(i, 1);
44             if (i < dest.size() && dest[i] == '\\')
45                 i++;
46         }
47     }
48 
49     return dest;
50 }
51 } // namespace
52 
StyleLine(StyleFile * style_file,std::string line)53 StyleLine::StyleLine(StyleFile *style_file, std::string line)
54     : styleFile_(style_file), line_(std::move(line)),
55       type_(StyleLineType::UNKNOWN) {}
56 
~StyleLine()57 StyleLine::~StyleLine() {}
58 
type()59 StyleLineType StyleLine::type() {
60     if (type_ != StyleLineType::UNKNOWN) {
61         return type_;
62     }
63 
64     auto line = fcitx::stringutils::trim(line_);
65     if (line.length() == 0) {
66         type_ = StyleLineType::SPACE;
67         return type_;
68     } else if (line[0] == '#') {
69         type_ = StyleLineType::COMMENT;
70         return type_;
71 
72     } else if (line.front() == '[' && line.back() == ']') {
73         type_ = StyleLineType::SECTION;
74         return type_;
75     }
76 
77     type_ = StyleLineType::KEY;
78     return type_;
79 }
80 
get_section(std::string & section)81 bool StyleLine::get_section(std::string &section) {
82     if (type() != StyleLineType::SECTION) {
83         return false;
84     }
85 
86     auto result = fcitx::stringutils::trim(line_);
87     // remove [ and ]
88     result.pop_back();
89     result = result.substr(1);
90     section = std::move(result);
91 
92     return true;
93 }
94 
get_key(std::string & key)95 bool StyleLine::get_key(std::string &key) {
96     if (type() != StyleLineType::KEY)
97         return false;
98 
99     // skip space ahead.
100     auto spos = line_.find_first_not_of(FCITX_WHITESPACE);
101     if (spos == std::string::npos) {
102         key = std::string();
103         return true;
104     }
105     size_t epos = spos;
106 
107     for (epos = spos; epos < line_.length(); epos++) {
108         if (line_[epos] == '\\') {
109             epos++;
110             continue;
111         }
112         if (line_[epos] == '=') {
113             break;
114         }
115     }
116     for (--epos; epos >= spos && fcitx::charutils::isspace(line_[epos]); epos--)
117         ;
118     if (!fcitx::charutils::isspace(line_[epos]))
119         epos++;
120 
121     if (spos < epos && epos <= line_.length()) {
122         key = unescape(line_.substr(spos, epos - spos));
123     } else
124         key = std::string();
125 
126     return true;
127 }
128 
get_value_position(std::string & str)129 static int get_value_position(std::string &str) {
130     unsigned int spos;
131     for (spos = 0; spos < str.length(); spos++) {
132         if (str[spos] == '\\') {
133             spos++;
134             continue;
135         }
136         if (str[spos] == '=') {
137             break;
138         }
139     }
140     if (spos >= str.length())
141         return true;
142     else
143         spos++;
144     for (; spos < str.length() && fcitx::charutils::isspace(str[spos]); spos++)
145         ;
146 
147     return spos;
148 }
149 
get_value(std::string & value)150 bool StyleLine::get_value(std::string &value) {
151     if (type() != StyleLineType::KEY)
152         return false;
153 
154     unsigned int spos = get_value_position(line_);
155     unsigned int epos = line_.length();
156 
157     value = unescape(line_.substr(spos, epos - spos));
158 
159     return true;
160 }
161 
get_value_array(std::vector<std::string> & value)162 bool StyleLine::get_value_array(std::vector<std::string> &value) {
163     if (type() != StyleLineType::KEY)
164         return false;
165 
166     unsigned int spos = get_value_position(line_);
167     unsigned int epos = line_.length();
168 
169     unsigned int head_of_element = spos;
170     for (unsigned int i = spos; i <= epos; i++) {
171         if (i < epos && line_[i] == '\\') {
172             i++;
173             continue;
174         }
175 
176         if (i == epos || line_[i] == ',') {
177             std::string str;
178             if (head_of_element == epos)
179                 str = std::string();
180             else
181                 str = unescape(
182                     line_.substr(head_of_element, i - head_of_element));
183             value.push_back(str);
184             head_of_element = i + 1;
185         }
186     }
187 
188     return true;
189 }
190 
StyleFile()191 StyleFile::StyleFile() { setupDefaultEntries(); }
192 
load(const std::string & filename)193 bool StyleFile::load(const std::string &filename) {
194     clear();
195     setupDefaultEntries();
196 
197     std::ifstream in_file(filename);
198     if (!in_file)
199         return false;
200 
201     clear();
202 
203     sections_.push_back(StyleLines());
204     StyleLines *section = &sections_[0];
205     unsigned int section_id = 0;
206 
207     char buf[MAX_LINE_LENGTH];
208     do {
209         in_file.getline(buf, MAX_LINE_LENGTH);
210         if (in_file.eof())
211             break;
212 
213         std::string dest = buf;
214         StyleLine line(this, dest);
215         StyleLineType type = line.type();
216 
217         if (type == StyleLineType::SECTION) {
218             sections_.push_back(StyleLines());
219             section = &sections_.back();
220             section_id++;
221         }
222 
223         section->push_back(line);
224 
225         if (section_id == 0) {
226             std::string key;
227             line.get_key(key);
228             if (key == "Title") {
229                 line.get_value(title_);
230             }
231         }
232     } while (!in_file.eof());
233 
234     in_file.close();
235 
236     return true;
237 }
238 
clear()239 void StyleFile::clear() {
240     title_ = std::string();
241     sections_.clear();
242 }
243 
title() const244 const std::string &StyleFile::title() const { return title_; }
245 
getString(std::string & value,std::string section,std::string key)246 bool StyleFile::getString(std::string &value, std::string section,
247                           std::string key) {
248     StyleSections::iterator it;
249     for (it = sections_.begin(); it != sections_.end(); it++) {
250         if (it->size() <= 0)
251             continue;
252 
253         std::string s, k;
254         (*it)[0].get_section(s);
255 
256         if (s != section)
257             continue;
258 
259         StyleLines::iterator lit;
260         for (lit = it->begin(); lit != it->end(); lit++) {
261             lit->get_key(k);
262             if (k == key) {
263                 lit->get_value(value);
264                 return true;
265             }
266         }
267     }
268 
269     return false;
270 }
271 
getStringArray(std::vector<std::string> & value,std::string section,std::string key)272 bool StyleFile::getStringArray(std::vector<std::string> &value,
273                                std::string section, std::string key) {
274     StyleLines *lines = findSection(section);
275     if (!lines)
276         return false;
277 
278     // find entry
279     StyleLines::iterator lit;
280     for (lit = lines->begin(); lit != lines->end(); lit++) {
281         std::string k;
282         lit->get_key(k);
283         if (k == key) {
284             lit->get_value_array(value);
285             return true;
286         }
287     }
288 
289     return false;
290 }
291 
getKeyList(std::vector<std::string> & keys,std::string section)292 bool StyleFile::getKeyList(std::vector<std::string> &keys,
293                            std::string section) {
294     StyleLines *lines = findSection(section);
295     if (!lines)
296         return false;
297 
298     StyleLines::iterator lit;
299     for (lit = lines->begin(); lit != lines->end(); lit++) {
300         if (lit->type() != StyleLineType::KEY)
301             continue;
302 
303         std::string key;
304         lit->get_key(key);
305         keys.push_back(key);
306     }
307     return true;
308 }
309 
key2kanaTable(std::string section)310 Key2KanaTable StyleFile::key2kanaTable(std::string section) {
311     Key2KanaTable table(title());
312 
313     std::vector<std::string> keys;
314     bool success = getKeyList(keys, section);
315     if (success) {
316         std::vector<std::string>::iterator it;
317         for (it = keys.begin(); it != keys.end(); it++) {
318             std::vector<std::string> array;
319             getStringArray(array, section, *it);
320             table.appendRule(*it, array);
321         }
322     }
323 
324     return table;
325 }
326 
setupDefaultEntries()327 void StyleFile::setupDefaultEntries() {
328     title_ = "User defined";
329     sections_.push_back(StyleLines());
330 
331     sections_.push_back(StyleLines());
332     StyleLines &newsec = sections_.back();
333     std::string str = std::string("Title") + std::string("=") + escape(title_);
334     newsec.push_back(StyleLine(this, str.c_str()));
335 }
336 
findSection(const std::string & section)337 StyleLines *StyleFile::findSection(const std::string &section) {
338     // find section
339     StyleSections::iterator it;
340     for (it = sections_.begin(); it != sections_.end(); it++) {
341         if (it->size() <= 0)
342             continue;
343 
344         std::string s;
345         (*it)[0].get_section(s);
346 
347         if (s == section)
348             return &(*it);
349     }
350 
351     return nullptr;
352 }
353