/src/wt/src/Wt/WMessageResources.C
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2008 Emweb bv, Herent, Belgium. |
3 | | * |
4 | | * See the LICENSE file for terms of use. |
5 | | */ |
6 | | #ifndef WT_CNOR |
7 | | #include <fstream> |
8 | | #include <cstring> |
9 | | |
10 | | #include "Wt/Exception/WInvalidFormatException.h" |
11 | | #include "Wt/Exception/WInvalidOperationException.h" |
12 | | |
13 | | #include "Wt/WLocale.h" |
14 | | #include "Wt/WLogger.h" |
15 | | #include "Wt/WMessageResources.h" |
16 | | #include "Wt/WStringStream.h" |
17 | | |
18 | | #include "DomElement.h" |
19 | | #include "WebUtils.h" |
20 | | |
21 | | #include "thirdparty/rapidxml/rapidxml.hpp" |
22 | | #include "thirdparty/rapidxml/rapidxml_print.hpp" |
23 | | |
24 | | using namespace Wt; |
25 | | using namespace Wt::rapidxml; |
26 | | |
27 | | #ifndef WT_NO_SPIRIT |
28 | | |
29 | | #include <boost/version.hpp> |
30 | | |
31 | | #if BOOST_VERSION < 103600 |
32 | | #include <boost/spirit.hpp> |
33 | | #include <boost/spirit/phoenix/binders.hpp> |
34 | | #else |
35 | | #include <boost/spirit/include/classic_core.hpp> |
36 | | #include <boost/spirit/include/classic_attribute.hpp> |
37 | | #include <boost/spirit/include/phoenix1_binders.hpp> |
38 | | #endif |
39 | | |
40 | | namespace { |
41 | | |
42 | | #if BOOST_VERSION < 103600 |
43 | | using namespace boost::spirit; |
44 | | #else |
45 | | using namespace boost::spirit::classic; |
46 | | #endif |
47 | | using namespace boost; |
48 | | |
49 | | struct CExpressionParser : grammar<CExpressionParser> |
50 | | { |
51 | | struct ParseState |
52 | | { |
53 | | bool condition_; |
54 | | }; |
55 | | |
56 | | CExpressionParser(::int64_t n, int &result, ParseState &state) : |
57 | 0 | n_(n), |
58 | 0 | result_(result), |
59 | 0 | state_(state) |
60 | 0 | {} |
61 | | |
62 | | struct value_closure : closure<value_closure, ::int64_t, ::int64_t> |
63 | | { |
64 | | member1 value; |
65 | | member2 condition; |
66 | | }; |
67 | | |
68 | | template <typename ScannerT> |
69 | | struct definition |
70 | | { |
71 | | definition(CExpressionParser const& self) |
72 | 0 | { |
73 | 0 | using namespace boost::spirit; |
74 | 0 | using namespace phoenix; |
75 | |
|
76 | 0 | group |
77 | 0 | = '(' |
78 | 0 | >> expression[group.value = arg1] |
79 | 0 | >> ')' |
80 | 0 | ; |
81 | | |
82 | | // A statement can end at the end of the line, or with a semicolon. |
83 | 0 | statement |
84 | 0 | = ( expression[bind(&CExpressionParser::set_result)(self, arg1)] |
85 | 0 | ) |
86 | 0 | >> (end_p | ';') |
87 | 0 | ; |
88 | |
|
89 | 0 | literal |
90 | 0 | = uint_p[literal.value = arg1] |
91 | 0 | ; |
92 | |
|
93 | 0 | factor |
94 | 0 | = literal[factor.value = arg1] |
95 | 0 | | group[factor.value = arg1] |
96 | 0 | | ch_p('n')[factor.value = bind(&CExpressionParser::get_n)(self)] |
97 | 0 | ; |
98 | |
|
99 | 0 | term |
100 | 0 | = factor[term.value = arg1] |
101 | 0 | >> *( ('*' >> factor[term.value *= arg1]) |
102 | 0 | | ('/' >> factor[bind(&CExpressionParser::safe_div)(self, term.value, arg1)]) |
103 | 0 | | ('%' >> factor[bind(&CExpressionParser::safe_rem)(self, term.value, arg1)]) |
104 | 0 | ) |
105 | 0 | ; |
106 | |
|
107 | 0 | additive_expression |
108 | 0 | = term[additive_expression.value = arg1] |
109 | 0 | >> *( ('+' >> term[additive_expression.value += arg1]) |
110 | 0 | | ('-' >> term[additive_expression.value -= arg1]) |
111 | 0 | ) |
112 | 0 | ; |
113 | |
|
114 | 0 | expression |
115 | 0 | = or_expression[expression.value = arg1] |
116 | 0 | [expression.condition = arg1] |
117 | 0 | >> !( '?' |
118 | 0 | >> expression[bind(&CExpressionParser::set_cond) |
119 | 0 | (self, expression.condition)] |
120 | 0 | [bind(&CExpressionParser::ternary_op) |
121 | 0 | (self, expression.value, arg1)] |
122 | 0 | >> ':' |
123 | 0 | >> expression[bind(&CExpressionParser::set_not_cond) |
124 | 0 | (self, expression.condition)] |
125 | 0 | [bind(&CExpressionParser::ternary_op) |
126 | 0 | (self, expression.value, arg1)] |
127 | 0 | ) |
128 | 0 | ; |
129 | |
|
130 | 0 | or_expression |
131 | 0 | = and_expression[or_expression.value = arg1] |
132 | 0 | >> *( "||" >> and_expression[bind(&CExpressionParser::or_op) |
133 | 0 | (self, or_expression.value, arg1)] ) |
134 | 0 | ; |
135 | |
|
136 | 0 | and_expression |
137 | 0 | = eq_expression[and_expression.value = arg1] |
138 | 0 | >> *( "&&" >> eq_expression[bind(&CExpressionParser::and_op) |
139 | 0 | (self, and_expression.value, arg1)] ) |
140 | 0 | ; |
141 | |
|
142 | 0 | eq_expression |
143 | 0 | = relational_expression[eq_expression.value = arg1] |
144 | 0 | >> *( ("==" >> relational_expression[bind(&CExpressionParser::eq_op) |
145 | 0 | (self, |
146 | 0 | eq_expression.value, |
147 | 0 | arg1)]) |
148 | 0 | | ("!=" >> relational_expression[bind(&CExpressionParser::neq_op) |
149 | 0 | (self, |
150 | 0 | eq_expression.value, |
151 | 0 | arg1)]) |
152 | 0 | ) |
153 | 0 | ; |
154 | |
|
155 | 0 | relational_expression |
156 | 0 | = additive_expression[relational_expression.value = arg1] |
157 | 0 | >> *( (">" >> additive_expression[bind(&CExpressionParser::gt_op) |
158 | 0 | (self, |
159 | 0 | relational_expression.value, |
160 | 0 | arg1)]) |
161 | 0 | | (">=" >> additive_expression[bind(&CExpressionParser::gte_op) |
162 | 0 | (self, |
163 | 0 | relational_expression.value, |
164 | 0 | arg1)]) |
165 | 0 | | ("<" >> additive_expression[bind(&CExpressionParser::lt_op) |
166 | 0 | (self, |
167 | 0 | relational_expression.value, |
168 | 0 | arg1)]) |
169 | 0 | | ("<=" >> additive_expression[bind(&CExpressionParser::lte_op) |
170 | 0 | (self, |
171 | 0 | relational_expression.value, |
172 | 0 | arg1)]) |
173 | 0 | ) |
174 | 0 | ; |
175 | 0 | } |
176 | | |
177 | | rule<ScannerT> const& |
178 | 0 | start() const { return statement; } |
179 | | |
180 | | rule<ScannerT> statement; |
181 | | rule<ScannerT, value_closure::context_t> expression, factor, |
182 | | group, literal, term, additive_expression, or_expression, and_expression, |
183 | | eq_expression, relational_expression; |
184 | | }; |
185 | | |
186 | | private: |
187 | 0 | ::int64_t get_n() const { return n_; } |
188 | | |
189 | 0 | void eq_op(::int64_t &x, ::int64_t y) const { x = x == y; } |
190 | 0 | void neq_op(::int64_t &x, ::int64_t y) const { x = x != y; } |
191 | | |
192 | 0 | void lt_op(::int64_t &x, ::int64_t y) const { x = x < y;} |
193 | 0 | void gt_op(::int64_t &x, ::int64_t y) const { x = x > y;} |
194 | 0 | void lte_op(::int64_t &x, ::int64_t y) const { x = x <= y;} |
195 | 0 | void gte_op(::int64_t &x, ::int64_t y) const { x = x >= y;} |
196 | | |
197 | | void ternary_op(::int64_t &result, ::int64_t y) const |
198 | 0 | { |
199 | 0 | if (state_.condition_) |
200 | 0 | result = y; |
201 | 0 | } |
202 | | |
203 | | void set_cond(::int64_t condition) const |
204 | 0 | { |
205 | 0 | state_.condition_ = condition; |
206 | 0 | } |
207 | | |
208 | | void set_not_cond(::int64_t condition) const |
209 | 0 | { |
210 | 0 | state_.condition_ = !condition; |
211 | 0 | } |
212 | | |
213 | 0 | void or_op(::int64_t &x, ::int64_t y) const { x = x || y; } |
214 | 0 | void and_op(::int64_t &x, ::int64_t y) const { x = x && y; } |
215 | | |
216 | | void safe_div(::int64_t& result, ::int64_t y) const |
217 | 0 | { |
218 | 0 | switch (y) { |
219 | 0 | case 0: |
220 | 0 | throw WInvalidOperationException("WMessageResources::evalPluralCase(): Cannot divide by 0"); |
221 | 0 | case -1: |
222 | 0 | result = -result; // Avoid overflow error when result is int64_t minimum value. |
223 | 0 | break; |
224 | 0 | default: |
225 | 0 | result = result / y; |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | void safe_rem(::int64_t& result, ::int64_t y) const |
230 | 0 | { |
231 | 0 | switch (y) { |
232 | 0 | case 0: |
233 | 0 | throw WInvalidOperationException("WMessageResources::evalPluralCase(): Cannot divide by 0"); |
234 | 0 | case -1: |
235 | 0 | result = 0; // Avoid overflow error when result is int64_t minimum value. |
236 | 0 | break; |
237 | 0 | default: |
238 | 0 | result = result % y; |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | 0 | void set_result(int result) const { result_ = result; } |
243 | | private : |
244 | | ::int64_t n_; |
245 | | int &result_; |
246 | | ParseState &state_; |
247 | | |
248 | | public : |
249 | 0 | int result() { return result_; } |
250 | | }; |
251 | | |
252 | | } // anonymous namespace |
253 | | |
254 | | #endif //WT_NO_SPIRIT |
255 | | |
256 | | namespace { |
257 | | char *copy_chars(const char *begin, const char *end, char *out) |
258 | 0 | { |
259 | 0 | while (begin != end) |
260 | 0 | *out++ = *begin++; |
261 | 0 | return out; |
262 | 0 | } |
263 | | |
264 | | std::string readElementContent(xml_node<> *x_parent, |
265 | | std::unique_ptr<char[]>& buf) |
266 | 0 | { |
267 | 0 | char *ptr = buf.get(); |
268 | |
|
269 | 0 | if (x_parent->type() == node_cdata) { |
270 | 0 | return std::string(x_parent->value(), x_parent->value_size()); |
271 | 0 | } else { |
272 | 0 | for (xml_node<> *x_child = x_parent->first_node(); |
273 | 0 | x_child; x_child = x_child->next_sibling()) { |
274 | 0 | if (x_child->type() == node_cdata) { |
275 | 0 | ptr = copy_chars(x_child->value(), |
276 | 0 | x_child->value() + x_child->value_size(), ptr); |
277 | 0 | } else { |
278 | 0 | Wt::Utils::fixSelfClosingTags(x_child); |
279 | 0 | ptr = print(ptr, *x_child, print_no_indenting); |
280 | 0 | } |
281 | 0 | } |
282 | |
|
283 | 0 | return std::string(buf.get(), ptr - buf.get()); |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | int attributeValueToInt(xml_attribute<> *x_attribute) |
288 | 0 | { |
289 | 0 | return Utils::stoi(std::string(x_attribute->value(), |
290 | 0 | x_attribute->value_size())); |
291 | 0 | } |
292 | | } |
293 | | |
294 | | namespace Wt { |
295 | | |
296 | | LOGGER("WMessageResources"); |
297 | | |
298 | | WMessageResources::WMessageResources(const std::string& path, |
299 | | bool loadInMemory) |
300 | 0 | : loadInMemory_(loadInMemory), |
301 | 0 | path_(path), |
302 | 0 | builtin_(nullptr) |
303 | 0 | { } |
304 | | |
305 | | WMessageResources::WMessageResources(const char *builtin) |
306 | 0 | : loadInMemory_(true), |
307 | 0 | builtin_(builtin) |
308 | 0 | { |
309 | 0 | std::istringstream s(builtin, std::ios::in | std::ios::binary); |
310 | 0 | #ifdef WT_THREADED |
311 | 0 | std::lock_guard<std::recursive_mutex> lock(resourceMutex_); |
312 | 0 | #endif |
313 | 0 | readResourceStream(s, resources_[""], "<internal resource bundle>"); |
314 | 0 | } |
315 | | |
316 | | std::set<std::string> WMessageResources::keys(const WLocale& locale) const |
317 | 0 | { |
318 | 0 | #ifdef WT_THREADED |
319 | 0 | std::lock_guard<std::recursive_mutex> lock(resourceMutex_); |
320 | 0 | #endif |
321 | | |
322 | 0 | load(locale); |
323 | |
|
324 | 0 | std::set<std::string> keys; |
325 | |
|
326 | 0 | for (auto& r : resources_) { |
327 | 0 | if (r.first == locale.name()) { |
328 | 0 | for (auto& k : r.second.map_) |
329 | 0 | keys.insert(k.first); |
330 | 0 | break; |
331 | 0 | } |
332 | 0 | } |
333 | |
|
334 | 0 | return keys; |
335 | 0 | } |
336 | | |
337 | | void WMessageResources::load(const WLocale& locale) const |
338 | 0 | { |
339 | 0 | if (!path_.empty()) { |
340 | 0 | Resource& target = resources_[locale.name()]; |
341 | 0 | std::string l = locale.name(); |
342 | |
|
343 | 0 | target.map_.clear(); |
344 | |
|
345 | 0 | for (;;) { |
346 | 0 | if (readResourceFile(l, target)) |
347 | 0 | break; |
348 | | |
349 | | /* try a lesser specified variant */ |
350 | 0 | std::string::size_type i = l.rfind('-'); |
351 | 0 | if (i != std::string::npos) |
352 | 0 | l.erase(i); |
353 | 0 | else { |
354 | 0 | if (locale.name().empty()) |
355 | 0 | LOG_ERROR("Could not load resource bundle: " << path_ << ".xml"); |
356 | 0 | break; |
357 | 0 | } |
358 | 0 | } |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | | void WMessageResources::hibernate() |
363 | 0 | { |
364 | 0 | if (!loadInMemory_) { |
365 | 0 | #ifdef WT_THREADED |
366 | 0 | std::lock_guard<std::recursive_mutex> lock(resourceMutex_); |
367 | 0 | #endif |
368 | 0 | resources_.clear(); |
369 | 0 | } |
370 | 0 | } |
371 | | |
372 | | LocalizedString WMessageResources::resolveKey(const WLocale& locale, const std::string& key) |
373 | | const |
374 | 0 | { |
375 | 0 | LocalizedString result = resolve(locale.name(), key); |
376 | 0 | if (result) |
377 | 0 | return result; |
378 | | |
379 | 0 | return resolve(std::string(), key); |
380 | 0 | } |
381 | | |
382 | | LocalizedString WMessageResources::resolve(const std::string& locale, const std::string& key) |
383 | | const |
384 | 0 | { |
385 | 0 | #ifdef WT_THREADED |
386 | 0 | std::lock_guard<std::recursive_mutex> lock(resourceMutex_); |
387 | 0 | #endif |
388 | | |
389 | 0 | if (resources_.find(locale) == resources_.end()) |
390 | 0 | load(locale); |
391 | |
|
392 | 0 | const Resource& res = resources_[locale]; |
393 | |
|
394 | 0 | KeyValuesMap::const_iterator j = res.map_.find(key); |
395 | 0 | if (j != res.map_.end()) { |
396 | 0 | if (j->second.size() > 1 ) |
397 | 0 | return LocalizedString{}; |
398 | 0 | return LocalizedString{j->second[0], TextFormat::XHTML}; |
399 | 0 | } |
400 | | |
401 | 0 | return LocalizedString{}; |
402 | 0 | } |
403 | | |
404 | | std::string WMessageResources::findCase(const std::vector<std::string> &cases, |
405 | | std::string pluralExpression, |
406 | | ::uint64_t amount) |
407 | | const |
408 | 0 | { |
409 | | #ifdef WT_NO_SPIRIT |
410 | | throw WException("WString::trn() requires the spirit library."); |
411 | | #else |
412 | 0 | int c = evalPluralCase(pluralExpression, amount); |
413 | |
|
414 | 0 | if (c > (int)cases.size() - 1 || c < 0) { |
415 | 0 | WStringStream error; |
416 | 0 | error << "Expression '" << pluralExpression << "' evaluates to '" |
417 | 0 | << c << "' for n=" << std::to_string(amount); |
418 | |
|
419 | 0 | if (c < 0) |
420 | 0 | error << " and values smaller than 0 are not allowed."; |
421 | 0 | else |
422 | 0 | error << " which is greater than the list of cases (size=" |
423 | 0 | << (int)cases.size() << ")."; |
424 | |
|
425 | 0 | throw WException(error.c_str()); |
426 | 0 | } |
427 | | |
428 | 0 | return cases[c]; |
429 | 0 | #endif // WT_NO_SPIRIT |
430 | 0 | } |
431 | | |
432 | | LocalizedString WMessageResources::resolvePluralKey(const WLocale& locale, |
433 | | const std::string& key, |
434 | | ::uint64_t amount) const |
435 | 0 | { |
436 | 0 | LocalizedString result = resolvePlural(locale.name(), key, amount); |
437 | 0 | if (result) |
438 | 0 | return result; |
439 | | |
440 | 0 | return resolvePlural(std::string(), key, amount); |
441 | 0 | } |
442 | | |
443 | | LocalizedString WMessageResources::resolvePlural(const std::string& locale, |
444 | | const std::string& key, |
445 | | ::uint64_t amount) const |
446 | 0 | { |
447 | 0 | #ifdef WT_THREADED |
448 | 0 | std::lock_guard<std::recursive_mutex> lock(resourceMutex_); |
449 | 0 | #endif |
450 | | |
451 | 0 | if (resources_.find(locale) == resources_.end()) |
452 | 0 | load(locale); |
453 | |
|
454 | 0 | Resource& res = resources_[locale]; |
455 | |
|
456 | 0 | KeyValuesMap::const_iterator j = res.map_.find(key); |
457 | 0 | if (j != res.map_.end()) { |
458 | 0 | if (j->second.size() != res.pluralCount_ ) |
459 | 0 | return LocalizedString{}; |
460 | 0 | std::string result = findCase(j->second, res.pluralExpression_, amount); |
461 | 0 | return LocalizedString{result, TextFormat::XHTML}; |
462 | 0 | } else |
463 | 0 | return LocalizedString{}; |
464 | 0 | } |
465 | | |
466 | | bool WMessageResources::readResourceFile(const std::string& locale, |
467 | | Resource& resource) const |
468 | 0 | { |
469 | 0 | if (!path_.empty()) { |
470 | 0 | std::string fileName |
471 | 0 | = path_ + (locale.length() > 0 ? "_" : "") + locale + ".xml"; |
472 | |
|
473 | 0 | std::ifstream s(fileName.c_str(), std::ios::binary); |
474 | 0 | return readResourceStream(s, resource, fileName); |
475 | 0 | } else { |
476 | 0 | return false; |
477 | 0 | } |
478 | 0 | } |
479 | | |
480 | | bool WMessageResources::readResourceStream(std::istream &s, |
481 | | Resource& resource, |
482 | | const std::string &fileName) const |
483 | 0 | { |
484 | 0 | if (!s) |
485 | 0 | return false; |
486 | | |
487 | 0 | s.seekg(0, std::ios::end); |
488 | 0 | int length = s.tellg(); |
489 | 0 | s.seekg(0, std::ios::beg); |
490 | |
|
491 | 0 | enum { UTF8, UTF16LE, UTF16BE } encoding = UTF8; |
492 | | |
493 | | // See if we have UTF16 BOM |
494 | 0 | if (length >= 2) { |
495 | 0 | unsigned char m1, m2; |
496 | 0 | m1 = s.get(); |
497 | 0 | m2 = s.get(); |
498 | |
|
499 | 0 | if (m1 == 0xFE && m2 == 0xFF) |
500 | 0 | encoding = UTF16BE; |
501 | 0 | else if (m1 == 0xFF && m2 == 0xFE) |
502 | 0 | encoding = UTF16LE; |
503 | 0 | else { |
504 | 0 | s.seekg(0, std::ios::beg); |
505 | 0 | } |
506 | 0 | } |
507 | |
|
508 | 0 | std::unique_ptr<char[]> text |
509 | 0 | (new char[encoding == UTF8 ? length + 1 : (length-2)*2 + 1]); |
510 | |
|
511 | 0 | if (encoding != UTF8) { |
512 | | // Transcode from UTF16 stream to CharEncoding::UTF8 text |
513 | 0 | const int BUFSIZE = 2048; |
514 | 0 | unsigned char buf[BUFSIZE]; |
515 | |
|
516 | 0 | unsigned long firstWord = 0; |
517 | 0 | char *out = text.get(); |
518 | |
|
519 | 0 | for (;;) { |
520 | 0 | s.read((char *)buf, BUFSIZE); |
521 | 0 | int read = s.gcount(); |
522 | |
|
523 | 0 | for (int i = 0; i < read; i += 2) { |
524 | 0 | unsigned long ch; |
525 | | |
526 | | // read next 2-byte char |
527 | 0 | if (encoding == UTF16LE) { |
528 | 0 | ch = buf[i+1]; |
529 | 0 | ch = (ch << 8) | buf[i]; |
530 | 0 | } else { |
531 | 0 | ch = buf[i]; |
532 | 0 | ch = (ch << 8) | buf[i+1]; |
533 | 0 | } |
534 | |
|
535 | 0 | if (firstWord) { |
536 | | // second word of multi-word |
537 | 0 | if (ch < 0xDC00 || ch > 0xDFFF) { |
538 | 0 | read = 0; |
539 | 0 | break; |
540 | 0 | } |
541 | | |
542 | 0 | unsigned long cp = 0x10000 + (((firstWord & 0x3FF) << 10) |
543 | 0 | | (ch & 0x3FF)); |
544 | |
|
545 | 0 | Wt::rapidxml::xml_document<>::insert_coded_character<0>(out, cp); |
546 | |
|
547 | 0 | firstWord = 0; |
548 | 0 | } else if (ch >= 0xD800 && ch <= 0xDBFF) { |
549 | | // first word of multi-word |
550 | 0 | firstWord = ch; |
551 | 0 | } else { |
552 | | // single-word |
553 | 0 | Wt::rapidxml::xml_document<>::insert_coded_character<0>(out, ch); |
554 | |
|
555 | 0 | firstWord = 0; |
556 | 0 | } |
557 | 0 | } |
558 | |
|
559 | 0 | if (read != BUFSIZE) |
560 | 0 | break; |
561 | 0 | } |
562 | |
|
563 | 0 | length = out - text.get(); |
564 | 0 | } else { |
565 | 0 | s.read(text.get(), length); |
566 | 0 | } |
567 | |
|
568 | 0 | text[length] = 0; |
569 | |
|
570 | 0 | try { |
571 | 0 | xml_document<> doc; |
572 | 0 | doc.parse<parse_no_string_terminators |
573 | 0 | | parse_comment_nodes |
574 | 0 | | parse_xhtml_entity_translation |
575 | 0 | | parse_validate_closing_tags>(text.get()); |
576 | |
|
577 | 0 | xml_node<> *x_root = doc.first_node("messages"); |
578 | 0 | if (!x_root) |
579 | 0 | throw parse_error("Expected <messages> root element", text.get()); |
580 | | |
581 | 0 | xml_attribute<> *x_nplurals = x_root->first_attribute("nplurals"); |
582 | 0 | xml_attribute<> *x_plural = x_root->first_attribute("plural"); |
583 | 0 | if (x_nplurals && !x_plural) |
584 | 0 | throw parse_error("Expected 'plural' attribute in <messages>", |
585 | 0 | x_root->value()); |
586 | 0 | if (x_plural && !x_nplurals) |
587 | 0 | throw parse_error("Expected 'nplurals' attribute in <messages>", |
588 | 0 | x_root->value()); |
589 | 0 | if (x_nplurals && x_plural) { |
590 | 0 | resource.pluralCount_ = attributeValueToInt(x_nplurals); |
591 | 0 | resource.pluralExpression_ |
592 | 0 | = std::string(x_plural->value(), x_plural->value_size()); |
593 | 0 | } else { |
594 | 0 | resource.pluralCount_ = 0; |
595 | 0 | } |
596 | | |
597 | | // factor 2 in case we expanded <span/> to <span></span> |
598 | 0 | std::unique_ptr<char[]> buf(new char[length * 2]); |
599 | |
|
600 | 0 | for (xml_node<> *x_message = x_root->first_node("message"); |
601 | 0 | x_message; x_message = x_message->next_sibling("message")) { |
602 | 0 | xml_attribute<> *x_id = x_message->first_attribute("id"); |
603 | 0 | if (!x_id) |
604 | 0 | throw parse_error("Missing message id", x_message->value()); |
605 | | |
606 | 0 | std::string id(x_id->value(), x_id->value_size()); |
607 | |
|
608 | 0 | xml_node<> *x_plural = x_message->first_node("plural"); |
609 | 0 | if (x_plural) { |
610 | 0 | if (resource.pluralCount_ == 0) |
611 | 0 | throw parse_error("Expected 'nplurals' attribute in <message>", |
612 | 0 | x_plural->value()); |
613 | | |
614 | 0 | resource.map_[id] = std::vector<std::string>(); |
615 | 0 | resource.map_[id].reserve(resource.pluralCount_); |
616 | |
|
617 | 0 | std::vector<bool> visited; |
618 | 0 | visited.reserve(resource.pluralCount_); |
619 | |
|
620 | 0 | for (unsigned i = 0; i < resource.pluralCount_; i++) { |
621 | 0 | resource.map_[id].push_back(std::string()); |
622 | 0 | visited.push_back(false); |
623 | 0 | } |
624 | |
|
625 | 0 | for (; x_plural; x_plural = x_plural->next_sibling("plural")) { |
626 | 0 | xml_attribute<> *x_case = x_plural->first_attribute("case"); |
627 | 0 | int c = attributeValueToInt(x_case); |
628 | 0 | if (c >= (int)resource.pluralCount_) |
629 | 0 | throw parse_error("The attribute 'case' used in <plural> is greater" |
630 | 0 | " than the nplurals <messages> attribute.", |
631 | 0 | x_plural->value()); |
632 | 0 | visited[c] = true; |
633 | 0 | resource.map_[id][c] = readElementContent(x_plural, buf); |
634 | 0 | } |
635 | | |
636 | 0 | for (unsigned i = 0; i < resource.pluralCount_; i++) |
637 | 0 | if (!visited[i]) |
638 | 0 | throw parse_error("Missing plural case in <message>", |
639 | 0 | x_message->value()); |
640 | 0 | } else { |
641 | 0 | resource.map_[id] = std::vector<std::string>(); |
642 | 0 | resource.map_[id].reserve(1); |
643 | 0 | resource.map_[id].push_back(readElementContent(x_message, buf)); |
644 | 0 | } |
645 | 0 | } |
646 | 0 | } catch (parse_error& e) { |
647 | 0 | LOG_ERROR("Error reading " << fileName |
648 | 0 | << ": at character " << (int)(e.where<char>() - text.get()) |
649 | 0 | << ": " << e.what()); |
650 | 0 | } |
651 | | |
652 | 0 | return true; |
653 | 0 | } |
654 | | |
655 | | int WMessageResources::evalPluralCase(const std::string &expression, |
656 | | ::uint64_t n) |
657 | 0 | { |
658 | 0 | int result = 0; |
659 | |
|
660 | 0 | #ifndef WT_NO_SPIRIT |
661 | 0 | CExpressionParser::ParseState state; |
662 | 0 | CExpressionParser p(n, result, state); |
663 | 0 | std::string tmp = expression; |
664 | 0 | if (tmp.length() > 1000) { |
665 | | // This is a temporary catch to #12374 |
666 | 0 | throw Wt::WInvalidFormatException("WMessageResources::evalPluralCase(): The input it too long"); |
667 | 0 | } |
668 | 0 | auto v = parse(tmp.begin(), tmp.end(), p, space_p); |
669 | 0 | if (!v.full) { |
670 | 0 | throw Wt::WInvalidFormatException("WMessageResources::evalPluralCase(): The parser encountered an invalid format"); |
671 | 0 | } |
672 | 0 | #endif // WT_NO_SPIRIT |
673 | | |
674 | 0 | return result; |
675 | 0 | } |
676 | | |
677 | | } |
678 | | #endif // WT_CNOR |
679 | | |